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+ import java .util .concurrent .atomic .AtomicBoolean ;
17+
18+ import org .hibernate .boot .Metadata ;
19+ import org .hibernate .boot .model .relational .Namespace ;
20+ import org .hibernate .boot .model .relational .Sequence ;
21+ import org .hibernate .mapping .Table ;
22+ import org .hibernate .service .ServiceRegistry ;
23+ import org .hibernate .tool .schema .TargetType ;
24+ import org .hibernate .tool .schema .spi .ExceptionHandler ;
25+ import org .hibernate .tool .schema .spi .ExecutionOptions ;
26+ import org .hibernate .tool .schema .spi .SchemaFilter ;
27+ import org .hibernate .tool .schema .spi .SchemaFilterProvider ;
28+ import org .hibernate .tool .schema .spi .SchemaManagementTool ;
29+ import org .hibernate .tool .schema .spi .SchemaMigrator ;
30+ import org .hibernate .tool .schema .spi .ScriptTargetOutput ;
31+ import org .hibernate .tool .schema .spi .TargetDescriptor ;
32+ import org .slf4j .Logger ;
33+ import org .slf4j .LoggerFactory ;
34+
35+ /**
36+ * Initializes Hibernate Envers audit tables when auditing is enabled. This class is responsible for
37+ * conditionally creating audit tables only when hibernate.integration.envers.enabled=true.
38+ */
39+ public class EnversAuditTableInitializer {
40+
41+ private static final Logger log = LoggerFactory .getLogger (EnversAuditTableInitializer .class );
42+
43+ private EnversAuditTableInitializer () {
44+
45+ }
46+
47+ /**
48+ * Checks if Envers is enabled and creates/updates audit tables as needed. This will Create or
49+ * Update audit tables if they don't exist - Update existing audit tables if the schema has
50+ * changed
51+ *
52+ * @param metadata Hibernate metadata containing entity mappings
53+ * @param hibernateProperties properties containing Envers configuration
54+ * @param serviceRegistry Hibernate service registry
55+ */
56+ public static void initialize (Metadata metadata , Properties hibernateProperties ,
57+ ServiceRegistry serviceRegistry ) {
58+
59+ if (!isEnversEnabled (hibernateProperties )) {
60+ log .debug ("Hibernate Envers is not enabled. Skipping audit table initialization." );
61+ return ;
62+ }
63+
64+ updateAuditTables (metadata , hibernateProperties , serviceRegistry );
65+ }
66+
67+ /**
68+ * Checks if Hibernate Envers is enabled in the configuration.
69+ *
70+ * @param properties Hibernate properties
71+ * @return true if Envers is enabled, false otherwise
72+ */
73+ private static boolean isEnversEnabled (Properties properties ) {
74+ String enversEnabled = properties .getProperty ("hibernate.integration.envers.enabled" );
75+ return "true" .equalsIgnoreCase (enversEnabled );
76+ }
77+
78+ /**
79+ * Creates or updates audit tables using Hibernate's {@link SchemaMigrator}. This method filters
80+ * to only process audit tables.
81+ *
82+ * @param metadata Hibernate metadata containing entity mappings (includes Envers audit
83+ * entities)
84+ * @param hibernateProperties Hibernate configuration properties
85+ * @param serviceRegistry Hibernate service registry
86+ */
87+ private static void updateAuditTables (Metadata metadata , Properties hibernateProperties ,
88+ ServiceRegistry serviceRegistry ) {
89+ String auditTablePrefix = hibernateProperties .getProperty ("org.hibernate.envers.audit_table_prefix" , "" );
90+ String auditTableSuffix = hibernateProperties .getProperty ("org.hibernate.envers.audit_table_suffix" , "_audit" );
91+
92+ Map <String , Object > settings = new HashMap <>((Map ) hibernateProperties );
93+ settings .put ("hibernate.hbm2ddl.schema_filter_provider" , buildSchemaFilterProvider (auditTablePrefix , auditTableSuffix ));
94+
95+ AtomicBoolean hasErrors = new AtomicBoolean (false );
96+ ExecutionOptions executionOptions = getExecutionOptions (settings , hasErrors );
97+ SchemaMigrator schemaMigrator = serviceRegistry .getService (SchemaManagementTool .class ).getSchemaMigrator (settings );
98+
99+ schemaMigrator .doMigration (metadata , executionOptions , getTargetDescriptor ());
100+
101+ if (hasErrors .get ()) {
102+ log .warn ("Envers audit table migration completed with errors." );
103+ } else {
104+ log .info ("Successfully created/updated Envers audit tables using Hibernate SchemaManagementTool." );
105+ }
106+ }
107+
108+ private static SchemaFilterProvider buildSchemaFilterProvider (String auditTablePrefix , String auditTableSuffix ) {
109+ String lowerPrefix = auditTablePrefix .toLowerCase ();
110+ String lowerSuffix = auditTableSuffix .toLowerCase ();
111+
112+ SchemaFilter auditFilter = new SchemaFilter () {
113+ @ Override
114+ public boolean includeNamespace (Namespace namespace ) {
115+ return true ;
116+ }
117+
118+ @ Override
119+ public boolean includeTable (Table table ) {
120+ String tableName = table .getName ();
121+ if (tableName == null ) {
122+ return false ;
123+ }
124+
125+ String lowerTableName = tableName .toLowerCase ();
126+
127+ if (lowerTableName .contains ("revision" ) || lowerTableName .equals ("revinfo" )) {
128+ return true ;
129+ }
130+
131+ boolean hasPrefix = lowerPrefix .isEmpty () || lowerTableName .startsWith (lowerPrefix );
132+ boolean hasSuffix = lowerSuffix .isEmpty () || lowerTableName .endsWith (lowerSuffix );
133+
134+ return hasPrefix && hasSuffix ;
135+ }
136+
137+ @ Override
138+ public boolean includeSequence (Sequence sequence ) {
139+ return false ;
140+ }
141+ };
142+
143+ return new SchemaFilterProvider () {
144+ @ Override public SchemaFilter getCreateFilter () { return auditFilter ; }
145+ @ Override public SchemaFilter getDropFilter () { return auditFilter ; }
146+ @ Override public SchemaFilter getMigrateFilter () { return auditFilter ; }
147+ @ Override public SchemaFilter getValidateFilter () { return auditFilter ; }
148+ };
149+ }
150+
151+ private static TargetDescriptor getTargetDescriptor () {
152+ return new TargetDescriptor () {
153+ @ Override
154+ public EnumSet <TargetType > getTargetTypes () {
155+ return EnumSet .of (TargetType .DATABASE );
156+ }
157+
158+ @ Override
159+ public ScriptTargetOutput getScriptTargetOutput () {
160+ return null ;
161+ }
162+ };
163+ }
164+
165+ private static ExecutionOptions getExecutionOptions (Map <String , Object > settings , AtomicBoolean hasErrors ) {
166+ return new ExecutionOptions () {
167+ @ Override
168+ public Map <String , Object > getConfigurationValues () {
169+ return settings ;
170+ }
171+
172+ @ Override
173+ public boolean shouldManageNamespaces () {
174+ return false ;
175+ }
176+
177+ @ Override
178+ public ExceptionHandler getExceptionHandler () {
179+ return throwable -> {
180+ hasErrors .set (true );
181+ log .warn ("Schema migration encountered an issue: {}" , throwable .getMessage ());
182+ };
183+ }
184+ };
185+ }
186+ }
0 commit comments