Skip to content

Commit 2b27b3c

Browse files
committed
TRUNK-6469: Add support for automatic initialization of Envers audit tables
1 parent c665da9 commit 2b27b3c

File tree

5 files changed

+405
-1
lines changed

5 files changed

+405
-1
lines changed

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

Lines changed: 12 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,14 @@ 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+
throw new RuntimeException(e);
247+
}
248+
}
237249
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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+
updateAuditTables(metadata, hibernateProperties, serviceRegistry);
56+
}
57+
58+
/**
59+
* Checks if Hibernate Envers is enabled in the configuration.
60+
*
61+
* @param properties Hibernate properties
62+
* @return true if Envers is enabled, false otherwise
63+
*/
64+
private static boolean isEnversEnabled(Properties properties) {
65+
String enversEnabled = properties.getProperty("hibernate.integration.envers.enabled");
66+
return "true".equalsIgnoreCase(enversEnabled);
67+
}
68+
69+
/**
70+
* Creates or updates audit tables using Hibernate's {@link SchemaMigrator}. This method filters
71+
* to only process audit tables.
72+
*
73+
* @param metadata Hibernate metadata containing entity mappings (includes Envers audit
74+
* entities)
75+
* @param hibernateProperties Hibernate configuration properties
76+
* @param serviceRegistry Hibernate service registry
77+
*/
78+
private static void updateAuditTables(Metadata metadata, Properties hibernateProperties,
79+
ServiceRegistry serviceRegistry) {
80+
String auditTablePrefix = hibernateProperties.getProperty("org.hibernate.envers.audit_table_prefix", "");
81+
String auditTableSuffix = hibernateProperties.getProperty("org.hibernate.envers.audit_table_suffix", "_audit");
82+
83+
ExecutionOptions executionOptions = getExecutionOptions((Map<String, Object>) (Map) hibernateProperties);
84+
85+
SchemaMigrator schemaMigrator = serviceRegistry.getService(SchemaManagementTool.class).getSchemaMigrator((Map<String, Object>) (Map)hibernateProperties);
86+
87+
TargetDescriptor targetDescriptor = getTargetDescriptor();
88+
89+
schemaMigrator.doMigration(metadata, executionOptions, contributed -> {
90+
String tableName = contributed.getExportIdentifier();
91+
if (tableName == null) {
92+
return false;
93+
}
94+
95+
String lowerTableName = tableName.toLowerCase();
96+
97+
if (lowerTableName.contains("revision") || lowerTableName.equals("revinfo")) {
98+
return true;
99+
}
100+
101+
String lowerPrefix = auditTablePrefix.toLowerCase();
102+
String lowerSuffix = auditTableSuffix.toLowerCase();
103+
104+
boolean hasPrefix = lowerPrefix.isEmpty() || lowerTableName.startsWith(lowerPrefix);
105+
boolean hasSuffix = lowerSuffix.isEmpty() || lowerTableName.endsWith(lowerSuffix);
106+
107+
return hasPrefix && hasSuffix;
108+
}, targetDescriptor);
109+
110+
log.info("Successfully created/updated Envers audit tables using Hibernate SchemaManagementTool.");
111+
}
112+
113+
private static TargetDescriptor getTargetDescriptor() {
114+
return new TargetDescriptor() {
115+
@Override
116+
public EnumSet<TargetType> getTargetTypes() {
117+
return EnumSet.of(TargetType.DATABASE);
118+
}
119+
120+
@Override
121+
public ScriptTargetOutput getScriptTargetOutput() {
122+
return null;
123+
}
124+
};
125+
}
126+
127+
private static ExecutionOptions getExecutionOptions(Map<String, Object> settings) {
128+
return new ExecutionOptions() {
129+
@Override
130+
public Map<String, Object> getConfigurationValues() {
131+
return settings;
132+
}
133+
134+
@Override
135+
public boolean shouldManageNamespaces() {
136+
return false;
137+
}
138+
139+
@Override
140+
public ExceptionHandler getExceptionHandler() {
141+
return throwable -> log.warn("Schema migration encountered an issue: {}", throwable.getMessage());
142+
}
143+
144+
@Override
145+
public SchemaFilter getSchemaFilter() {
146+
return SchemaFilter.ALL;
147+
}
148+
};
149+
}
150+
}

api/src/main/resources/hibernate.default.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,4 @@ hibernate.id.new_generator_mappings=false
6060
# Hibernate envers options
6161
hibernate.integration.envers.enabled=false
6262
org.hibernate.envers.revision_listener=org.openmrs.api.db.hibernate.envers.OpenmrsRevisionEntityListener
63+
org.hibernate.envers.audit_table_suffix=_audit

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)