Skip to content

Commit 07955a5

Browse files
committed
TRUNK-6469: Add support for automatic initialization of Envers audit tables
1 parent 8cd859a commit 07955a5

File tree

4 files changed

+410
-1
lines changed

4 files changed

+410
-1
lines changed

api/src/main/java/org/openmrs/api/db/hibernate/HibernateSessionFactoryBean.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.openmrs.api.context.Context;
3333
import org.openmrs.module.Module;
3434
import org.openmrs.module.ModuleFactory;
35+
import org.openmrs.util.EnversAuditTableInitializer;
3536
import org.openmrs.util.OpenmrsUtil;
3637
import org.slf4j.Logger;
3738
import org.slf4j.LoggerFactory;
@@ -221,6 +222,7 @@ public void destroy() throws HibernateException {
221222
public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactory,
222223
SessionFactoryServiceRegistry serviceRegistry) {
223224
this.metadata = metadata;
225+
generateEnversAuditTables(metadata, serviceRegistry);
224226
}
225227

226228
@Override
@@ -234,4 +236,13 @@ public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactor
234236
public Metadata getMetadata() {
235237
return metadata;
236238
}
239+
240+
private void generateEnversAuditTables(Metadata metadata, SessionFactoryServiceRegistry serviceRegistry) {
241+
try {
242+
Properties hibernateProperties = getHibernateProperties();
243+
EnversAuditTableInitializer.initialize(metadata, hibernateProperties, serviceRegistry);
244+
} catch (Exception e) {
245+
log.error("Failed to initialize Envers audit tables", e);
246+
}
247+
}
237248
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/**
2+
* This Source Code Form is subject to the terms of the Mozilla Public License,
3+
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
4+
* obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
5+
* the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
6+
*
7+
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
8+
* graphic logo is a trademark of OpenMRS Inc.
9+
*/
10+
package org.openmrs.util;
11+
12+
import java.util.EnumSet;
13+
import java.util.HashMap;
14+
import java.util.Map;
15+
import java.util.Properties;
16+
17+
import org.hibernate.boot.Metadata;
18+
import org.hibernate.service.ServiceRegistry;
19+
import org.hibernate.tool.schema.TargetType;
20+
import org.hibernate.tool.schema.spi.ExceptionHandler;
21+
import org.hibernate.tool.schema.spi.ExecutionOptions;
22+
import org.hibernate.tool.schema.spi.SchemaFilter;
23+
import org.hibernate.tool.schema.spi.SchemaManagementTool;
24+
import org.hibernate.tool.schema.spi.SchemaMigrator;
25+
import org.hibernate.tool.schema.spi.ScriptTargetOutput;
26+
import org.hibernate.tool.schema.spi.TargetDescriptor;
27+
import org.slf4j.Logger;
28+
import org.slf4j.LoggerFactory;
29+
30+
/**
31+
* Initializes Hibernate Envers audit tables when auditing is enabled. This class is responsible for
32+
* conditionally creating audit tables only when hibernate.integration.envers.enabled=true.
33+
*/
34+
public class EnversAuditTableInitializer {
35+
36+
private static final Logger log = LoggerFactory.getLogger(EnversAuditTableInitializer.class);
37+
38+
/**
39+
* Checks if Envers is enabled and creates/updates audit tables as needed. This will Create or
40+
* Update audit tables if they don't exist - Update existing audit tables if the schema has
41+
* changed
42+
*
43+
* @param metadata Hibernate metadata containing entity mappings
44+
* @param hibernateProperties properties containing Envers configuration
45+
* @param serviceRegistry Hibernate service registry
46+
*/
47+
public static void initialize(Metadata metadata, Properties hibernateProperties,
48+
ServiceRegistry serviceRegistry) {
49+
50+
if (!isEnversEnabled(hibernateProperties)) {
51+
log.debug("Hibernate Envers is not enabled. Skipping audit table initialization.");
52+
return;
53+
}
54+
55+
try {
56+
updateAuditTables(metadata, hibernateProperties, serviceRegistry);
57+
log.info("Envers audit table initialization completed successfully.");
58+
}
59+
catch (Exception e) {
60+
log.error("Failed to initialize Envers audit tables. ", e);
61+
}
62+
}
63+
64+
/**
65+
* Checks if Hibernate Envers is enabled in the configuration.
66+
*
67+
* @param properties Hibernate properties
68+
* @return true if Envers is enabled, false otherwise
69+
*/
70+
private static boolean isEnversEnabled(Properties properties) {
71+
String enversEnabled = properties.getProperty("hibernate.integration.envers.enabled");
72+
return "true".equalsIgnoreCase(enversEnabled);
73+
}
74+
75+
/**
76+
* Creates or updates audit tables using Hibernate's {@link SchemaMigrator}. This method filters
77+
* to only process audit tables.
78+
*
79+
* @param metadata Hibernate metadata containing entity mappings (includes Envers audit
80+
* entities)
81+
* @param hibernateProperties Hibernate configuration properties
82+
* @param serviceRegistry Hibernate service registry
83+
* @throws Exception if table creation/update fails
84+
*/
85+
private static void updateAuditTables(Metadata metadata, Properties hibernateProperties,
86+
ServiceRegistry serviceRegistry) throws Exception {
87+
String auditTablePrefix = hibernateProperties.getProperty("org.hibernate.envers.audit_table_prefix", "");
88+
String auditTableSuffix = hibernateProperties.getProperty("org.hibernate.envers.audit_table_suffix", "_audit");
89+
90+
Map<String, Object> settings = new HashMap<>();
91+
for (String key : hibernateProperties.stringPropertyNames()) {
92+
settings.put(key, hibernateProperties.getProperty(key));
93+
}
94+
95+
ExecutionOptions executionOptions = getExecutionOptions(settings);
96+
97+
SchemaMigrator schemaMigrator = serviceRegistry.getService(SchemaManagementTool.class).getSchemaMigrator(settings);
98+
99+
TargetDescriptor targetDescriptor = getTargetDescriptor();
100+
101+
schemaMigrator.doMigration(metadata, executionOptions, contributed -> {
102+
String tableName = contributed.getExportIdentifier();
103+
if (tableName == null) {
104+
return false;
105+
}
106+
107+
String lowerTableName = tableName.toLowerCase();
108+
109+
if (lowerTableName.contains("revision") || lowerTableName.equals("revinfo")) {
110+
return true;
111+
}
112+
113+
String lowerPrefix = auditTablePrefix.toLowerCase();
114+
String lowerSuffix = auditTableSuffix.toLowerCase();
115+
116+
boolean hasPrefix = lowerPrefix.isEmpty() || lowerTableName.startsWith(lowerPrefix);
117+
boolean hasSuffix = lowerSuffix.isEmpty() || lowerTableName.endsWith(lowerSuffix);
118+
119+
return hasPrefix && hasSuffix;
120+
}, targetDescriptor);
121+
122+
log.info("Successfully created/updated Envers audit tables using Hibernate SchemaManagementTool.");
123+
}
124+
125+
private static TargetDescriptor getTargetDescriptor() {
126+
return new TargetDescriptor() {
127+
@Override
128+
public EnumSet<TargetType> getTargetTypes() {
129+
return EnumSet.of(TargetType.DATABASE);
130+
}
131+
132+
@Override
133+
public ScriptTargetOutput getScriptTargetOutput() {
134+
return null;
135+
}
136+
};
137+
}
138+
139+
private static ExecutionOptions getExecutionOptions(Map<String, Object> settings) {
140+
return new ExecutionOptions() {
141+
@Override
142+
public Map<String, Object> getConfigurationValues() {
143+
return settings;
144+
}
145+
146+
@Override
147+
public boolean shouldManageNamespaces() {
148+
return false;
149+
}
150+
151+
@Override
152+
public ExceptionHandler getExceptionHandler() {
153+
return throwable -> log.warn("Schema migration encountered an issue: {}", throwable.getMessage());
154+
}
155+
156+
@Override
157+
public SchemaFilter getSchemaFilter() {
158+
return SchemaFilter.ALL;
159+
}
160+
};
161+
}
162+
}

api/src/test/java/org/openmrs/util/DatabaseIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public class DatabaseIT implements LiquibaseProvider {
4444
protected static final String PASSWORD = "test";
4545

4646
@BeforeEach
47-
public void setup() throws SQLException, ClassNotFoundException {
47+
public void setup() throws Exception {
4848
this.initializeDatabase();
4949
}
5050

0 commit comments

Comments
 (0)