• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

openmrs / openmrs-core / 22296336943

23 Feb 2026 07:12AM UTC coverage: 63.442% (-0.05%) from 63.493%
22296336943

push

github

web-flow
TRUNK-6469: Add support for automatic initialization of Envers audit tables (#5468) (#5831)

14 of 60 new or added lines in 3 files covered. (23.33%)

246 existing lines in 4 files now uncovered.

23202 of 36572 relevant lines covered (63.44%)

0.63 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

12.24
/api/src/main/java/org/openmrs/util/EnversAuditTableInitializer.java
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);
1✔
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)) {
1✔
60
                        log.debug("Hibernate Envers is not enabled. Skipping audit table initialization.");
1✔
61
                        return;
1✔
62
                }
63

NEW
64
                updateAuditTables(metadata, hibernateProperties, serviceRegistry);
×
NEW
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");
1✔
75
                return "true".equalsIgnoreCase(enversEnabled);
1✔
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) {
NEW
89
                String auditTablePrefix = hibernateProperties.getProperty("org.hibernate.envers.audit_table_prefix", "");
×
NEW
90
                String auditTableSuffix = hibernateProperties.getProperty("org.hibernate.envers.audit_table_suffix", "_audit");
×
91

NEW
92
                Map<String, Object> settings = new HashMap<>((Map) hibernateProperties);
×
NEW
93
                settings.put("hibernate.hbm2ddl.schema_filter_provider", buildSchemaFilterProvider(auditTablePrefix, auditTableSuffix));
×
94

NEW
95
                AtomicBoolean hasErrors = new AtomicBoolean(false);
×
NEW
96
                ExecutionOptions executionOptions = getExecutionOptions(settings, hasErrors);
×
NEW
97
                SchemaMigrator schemaMigrator = serviceRegistry.getService(SchemaManagementTool.class).getSchemaMigrator(settings);
×
98

NEW
99
                schemaMigrator.doMigration(metadata, executionOptions, getTargetDescriptor());
×
100

NEW
101
                if (hasErrors.get()) {
×
NEW
102
                        log.warn("Envers audit table migration completed with errors.");
×
103
                } else {
NEW
104
                        log.info("Successfully created/updated Envers audit tables using Hibernate SchemaManagementTool.");
×
105
                }
NEW
106
        }
×
107

108
        private static SchemaFilterProvider buildSchemaFilterProvider(String auditTablePrefix, String auditTableSuffix) {
NEW
109
                String lowerPrefix = auditTablePrefix.toLowerCase();
×
NEW
110
                String lowerSuffix = auditTableSuffix.toLowerCase();
×
111

NEW
112
                SchemaFilter auditFilter = new SchemaFilter() {
×
113
                        @Override
114
                        public boolean includeNamespace(Namespace namespace) {
NEW
115
                                return true;
×
116
                        }
117

118
                        @Override
119
                        public boolean includeTable(Table table) {
NEW
120
                                String tableName = table.getName();
×
NEW
121
                                if (tableName == null) {
×
NEW
122
                                        return false;
×
123
                                }
124

NEW
125
                                String lowerTableName = tableName.toLowerCase();
×
126

NEW
127
                                if (lowerTableName.contains("revision") || lowerTableName.equals("revinfo")) {
×
NEW
128
                                        return true;
×
129
                                }
130

NEW
131
                                boolean hasPrefix = lowerPrefix.isEmpty() || lowerTableName.startsWith(lowerPrefix);
×
NEW
132
                                boolean hasSuffix = lowerSuffix.isEmpty() || lowerTableName.endsWith(lowerSuffix);
×
133

NEW
134
                                return hasPrefix && hasSuffix;
×
135
                        }
136

137
                        @Override
138
                        public boolean includeSequence(Sequence sequence) {
NEW
139
                                return false;
×
140
                        }
141
                };
142

NEW
143
                return new SchemaFilterProvider() {
×
NEW
144
                        @Override public SchemaFilter getCreateFilter() { return auditFilter; }
×
NEW
145
                        @Override public SchemaFilter getDropFilter() { return auditFilter; }
×
NEW
146
                        @Override public SchemaFilter getMigrateFilter() { return auditFilter; }
×
NEW
147
                        @Override public SchemaFilter getValidateFilter() { return auditFilter; }
×
148
                };
149
        }
150

151
        private static TargetDescriptor getTargetDescriptor() {
NEW
152
                return new TargetDescriptor() {
×
153
                        @Override
154
                        public EnumSet<TargetType> getTargetTypes() {
NEW
155
                                return EnumSet.of(TargetType.DATABASE);
×
156
                        }
157

158
                        @Override
159
                        public ScriptTargetOutput getScriptTargetOutput() {
NEW
160
                                return null;
×
161
                        }
162
                };
163
        }
164

165
        private static ExecutionOptions getExecutionOptions(Map<String, Object> settings, AtomicBoolean hasErrors) {
NEW
166
                return new ExecutionOptions() {
×
167
                        @Override
168
                        public Map<String, Object> getConfigurationValues() {
NEW
169
                                return settings;
×
170
                        }
171

172
                        @Override
173
                        public boolean shouldManageNamespaces() {
NEW
174
                                return false;
×
175
                        }
176

177
                        @Override
178
                        public ExceptionHandler getExceptionHandler() {
NEW
179
                                return throwable -> {
×
NEW
180
                                        hasErrors.set(true);
×
NEW
181
                                        log.warn("Schema migration encountered an issue: {}", throwable.getMessage());
×
NEW
182
                                };
×
183
                        }
184
                };
185
        }
186
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc