From 65e466187b653ec0b1a1b5303aa257c0491da0b2 Mon Sep 17 00:00:00 2001 From: Michael Seaton Date: Wed, 8 Nov 2023 22:19:31 -0500 Subject: [PATCH] [#255] Fixing 'liquibase' domain issues against Core 2.5.5+. (#256) --- README.md | 1 + api-2.5/pom.xml | 106 +++++++++++++++++ .../api/loaders/LiquibaseLoader2_5.java | 96 +++++++++++++++ .../LiquibaseLoader25IntegrationTest.java | 110 ++++++++++++++++++ .../src/test/resources/liquibase-schema.sql | 27 +++++ api-2.5/src/test/resources/log4j2.xml | 34 ++++++ .../configuration/liquibase/liquibase.xml | 16 +++ .../liquibase/sql/test_changes_1.sql | 1 + .../liquibase/sql/test_changes_2.sql | 1 + .../module/initializer/api/OrderedFile.java | 2 +- .../api/loaders/AddressHierarchyLoader.java | 3 +- .../api/loaders/LiquibaseLoader.java | 7 +- omod/pom.xml | 6 + omod/src/main/resources/config.xml | 6 +- pom.xml | 2 + validator/pom.xml | 7 ++ 16 files changed, 419 insertions(+), 6 deletions(-) create mode 100644 api-2.5/pom.xml create mode 100644 api-2.5/src/main/java/org/openmrs/module/initializer/api/loaders/LiquibaseLoader2_5.java create mode 100644 api-2.5/src/test/java/org/openmrs/module/initializer/api/loaders/LiquibaseLoader25IntegrationTest.java create mode 100644 api-2.5/src/test/resources/liquibase-schema.sql create mode 100644 api-2.5/src/test/resources/log4j2.xml create mode 100644 api-2.5/src/test/resources/testAppDataDir/configuration/liquibase/liquibase.xml create mode 100644 api-2.5/src/test/resources/testAppDataDir/configuration/liquibase/sql/test_changes_1.sql create mode 100644 api-2.5/src/test/resources/testAppDataDir/configuration/liquibase/sql/test_changes_2.sql diff --git a/README.md b/README.md index f312c1ae..5c3c0b71 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,7 @@ See the [documentation on Initializer's logging properties](readme/rtprops.md#lo #### Version 2.7.0 * Added support for 'queues' domain. * Added support for 'addresshierarchy' domain. +* Fix for Liquibase Loader to ensure compatibility with OpenMRS versions 2.5.5+ #### Version 2.6.0 * Added support for 'cohorttypes' and 'cohortattributetypes' domains. diff --git a/api-2.5/pom.xml b/api-2.5/pom.xml new file mode 100644 index 00000000..be0f4ebf --- /dev/null +++ b/api-2.5/pom.xml @@ -0,0 +1,106 @@ + + + + initializer + org.openmrs.module + 2.7.0-SNAPSHOT + + 4.0.0 + + initializer-api-2.5 + jar + Initializer API 2.5 + API 2.5 project for Initializer + + + ${openmrsVersion2.5} + 2.2.0 + + + + + + org.openmrs.test + openmrs-test + pom + ${openmrsPlatformVersion} + test + + + org.powermock + powermock-api-mockito2 + + + + + + ${project.parent.groupId} + ${project.parent.artifactId}-api + ${project.parent.version} + provided + + + + ${project.parent.groupId} + ${project.parent.artifactId}-api + ${project.parent.version} + test + test-jar + + + + ${project.parent.groupId} + ${project.parent.artifactId}-api-2.4 + ${project.parent.version} + provided + + + + ${project.parent.groupId} + ${project.parent.artifactId}-api-2.3 + ${project.parent.version} + provided + + + + ${project.parent.groupId} + ${project.parent.artifactId}-api-2.3 + ${project.parent.version} + test + test-jar + + + + ${project.parent.groupId} + ${project.parent.artifactId}-api-2.2 + ${project.parent.version} + provided + + + + ${project.parent.groupId} + ${project.parent.artifactId}-api-2.2 + ${project.parent.version} + test + test-jar + + + + org.openmrs.module + fhir2-api-2.5 + ${fhir2Version} + provided + + + + org.openmrs.module + datafilter-api + ${datafilterVersion} + provided + + + + + diff --git a/api-2.5/src/main/java/org/openmrs/module/initializer/api/loaders/LiquibaseLoader2_5.java b/api-2.5/src/main/java/org/openmrs/module/initializer/api/loaders/LiquibaseLoader2_5.java new file mode 100644 index 00000000..7e4c9526 --- /dev/null +++ b/api-2.5/src/main/java/org/openmrs/module/initializer/api/loaders/LiquibaseLoader2_5.java @@ -0,0 +1,96 @@ +package org.openmrs.module.initializer.api.loaders; + +import org.openmrs.annotation.OpenmrsProfile; +import org.openmrs.api.context.Context; +import org.openmrs.liquibase.ChangeSetExecutorCallback; +import org.openmrs.module.initializer.Domain; +import org.openmrs.module.initializer.api.ConfigDirUtil; +import org.openmrs.util.DatabaseUpdater; +import org.openmrs.util.OpenmrsUtil; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +@OpenmrsProfile(openmrsPlatformVersion = "2.5.5") +public class LiquibaseLoader2_5 extends BaseFileLoader { + + public static final String LIQUIBASE_FILE_NAME = "liquibase"; + + @Override + protected Domain getDomain() { + return Domain.LIQUIBASE; + } + + @Override + protected String getFileExtension() { + return "xml"; + } + + @Override + protected void load(File file) throws Exception { + if (file.getName().equalsIgnoreCase(LIQUIBASE_FILE_NAME + "." + getFileExtension())) { + // Because liquibase uses the path provided as an identifier of the changeset, we need to relativize it + // This ensures that liquibase changesets are not re-executed if the absolute path on the server changes + String absolutePath = file.getAbsolutePath(); + String relativePath = getPathRelativeToApplicationDataDirectory(file); + updateExistingLiquibaseChangeLogPathsIfNeeded(absolutePath, relativePath); + try { + DatabaseUpdater.executeChangelog(relativePath, (ChangeSetExecutorCallback) null); + } + catch (Exception e) { + log.error("An error occurred executing liquibase file: " + file, e); + throw e; + } + } + } + + /** + * If a particular changeset file is being executed, and this is referenced by the given oldPath, + * then update that changeset to reference the given newPath + */ + protected void updateExistingLiquibaseChangeLogPathsIfNeeded(String oldPath, String newPath) { + log.debug("Checking if liquibase filenames need to be updated for " + oldPath); + int numRows = sqlCount("select count(*) from liquibasechangelog where filename = '" + oldPath + "'"); + if (numRows > 0) { + log.warn("Liquibase filename update is needed for " + oldPath + ". Updating to " + newPath); + sqlUpdate("update liquibasechangelog set filename = '" + newPath + "' where filename = '" + oldPath + "'"); + } else { + log.debug("Liquibase filename update is not required"); + } + } + + /** + * @param file the file to relativize + * @return the path of the given file, relativized to the OpenMRS application data directory + */ + protected String getPathRelativeToApplicationDataDirectory(File file) { + Path appDataDirPath = Paths.get(OpenmrsUtil.getApplicationDataDirectory()); + Path liquibaseFilePath = file.toPath(); + return appDataDirPath.relativize(liquibaseFilePath).toString(); + } + + /** + * @param sql the sql to execute + * @return the results of that execution, where the first column of the first row is returned as an + * integer count + */ + private int sqlCount(String sql) { + List> results = Context.getAdministrationService().executeSQL(sql, true); + Object singleResult = results.get(0).get(0); + return Integer.parseInt(singleResult.toString()); + } + + /** + * @param sql the sql to execute as a database update operation + */ + private void sqlUpdate(String sql) { + Context.getAdministrationService().executeSQL(sql, false); + } + + @Override + public ConfigDirUtil getDirUtil() { + return new ConfigDirUtil(iniz.getConfigDirPath(), iniz.getChecksumsDirPath(), getDomainName(), true); + } +} diff --git a/api-2.5/src/test/java/org/openmrs/module/initializer/api/loaders/LiquibaseLoader25IntegrationTest.java b/api-2.5/src/test/java/org/openmrs/module/initializer/api/loaders/LiquibaseLoader25IntegrationTest.java new file mode 100644 index 00000000..390974ab --- /dev/null +++ b/api-2.5/src/test/java/org/openmrs/module/initializer/api/loaders/LiquibaseLoader25IntegrationTest.java @@ -0,0 +1,110 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under + * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. + * + * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS + * graphic logo is a trademark of OpenMRS Inc. + */ +package org.openmrs.module.initializer.api.loaders; + +import org.apache.commons.io.IOUtils; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.openmrs.api.AdministrationService; +import org.openmrs.module.initializer.DomainBaseModuleContextSensitiveTest; +import org.openmrs.util.OpenmrsClassLoader; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +public class LiquibaseLoader25IntegrationTest extends DomainBaseModuleContextSensitiveTest { + + @Autowired + private LiquibaseLoader2_5 loader; + + @Autowired + @Qualifier("adminService") + private AdministrationService adminService; + + @Before + public void setup() throws Exception { + System.setProperty("useInMemoryDatabase", "true"); + ClassLoader cl = OpenmrsClassLoader.getInstance(); + String schemaSql = IOUtils.resourceToString("liquibase-schema.sql", StandardCharsets.UTF_8, cl); + adminService.executeSQL(schemaSql, false); + } + + @After + public void teardown() { + adminService.executeSQL("drop table LIQUIBASECHANGELOG", false); + adminService.executeSQL("drop table LIQUIBASECHANGELOGLOCK", false); + } + + @Test + public void load_shouldExecuteNewChangeSet() { + String relativePath = "configuration/liquibase/liquibase.xml"; + String absolutePath = getAppDataDirPath() + relativePath; + Assert.assertEquals(0, numChangeLogEntries(absolutePath)); + Assert.assertEquals(0, numChangeLogEntries(relativePath)); + Assert.assertNull(adminService.getGlobalProperty("test_changes_1")); + Assert.assertNull(adminService.getGlobalProperty("test_changes_2")); + loader.load(); + Assert.assertEquals(0, numChangeLogEntries(absolutePath)); + Assert.assertEquals(2, numChangeLogEntries(relativePath)); + Assert.assertEquals("true", adminService.getGlobalProperty("test_changes_1")); + Assert.assertEquals("true", adminService.getGlobalProperty("test_changes_2")); + } + + @Test + public void load_shouldUpdateAbsolutePathToRelativePathIfNeeded() { + String relativePath = "configuration/liquibase/liquibase.xml"; + String absolutePath = getAppDataDirPath() + relativePath; + insertExistingChangeLogEntry(absolutePath); + Assert.assertEquals(1, numChangeLogEntries(absolutePath)); + Assert.assertEquals(0, numChangeLogEntries(relativePath)); + loader.updateExistingLiquibaseChangeLogPathsIfNeeded(absolutePath, relativePath); + Assert.assertEquals(0, numChangeLogEntries(absolutePath)); + Assert.assertEquals(1, numChangeLogEntries(relativePath)); + } + + private int numChangeLogEntries(String filename) { + List> ret = adminService + .executeSQL("select count(*) from liquibasechangelog where filename = '" + filename + "'", true); + return Integer.parseInt(ret.get(0).get(0).toString()); + } + + private void insertExistingChangeLogEntry(String filename) { + StringBuilder sb = new StringBuilder(); + sb.append("INSERT INTO LIQUIBASECHANGELOG ("); + sb.append(" ID, "); + sb.append(" AUTHOR, "); + sb.append(" FILENAME, "); + sb.append(" DATEEXECUTED, "); + sb.append(" ORDEREXECUTED, "); + sb.append(" EXECTYPE, "); + sb.append(" MD5SUM, "); + sb.append(" DESCRIPTION, "); + sb.append(" COMMENTS, "); + sb.append(" LIQUIBASE "); + sb.append(") "); + sb.append("values ("); + sb.append(" 'previousSqlFileChangeset', "); + sb.append(" 'iniz', "); + sb.append(" '").append(filename).append("', "); + sb.append(" '2023-11-01 00:00:00', "); + sb.append(" 1, "); + sb.append(" 'EXECUTED', "); + sb.append(" '8:4019e34234869ff55c81acf9d779f2a7', "); + sb.append(" 'sqlFile', "); + sb.append(" '', "); + sb.append(" '4.4.1'"); + sb.append(")"); + adminService.executeSQL(sb.toString(), false); + } +} diff --git a/api-2.5/src/test/resources/liquibase-schema.sql b/api-2.5/src/test/resources/liquibase-schema.sql new file mode 100644 index 00000000..037113bd --- /dev/null +++ b/api-2.5/src/test/resources/liquibase-schema.sql @@ -0,0 +1,27 @@ +CREATE TABLE LIQUIBASECHANGELOG +( + ID varchar(255) NOT NULL, + AUTHOR varchar(255) NOT NULL, + FILENAME varchar(255) NOT NULL, + DATEEXECUTED datetime NOT NULL, + ORDEREXECUTED int(11) NOT NULL, + EXECTYPE varchar(10) NOT NULL, + MD5SUM varchar(35), + DESCRIPTION varchar(255), + COMMENTS varchar(255), + TAG varchar(255), + LIQUIBASE varchar(20), + CONTEXTS varchar(255), + LABELS varchar(255), + DEPLOYMENT_ID varchar(10), + PRIMARY KEY (ID, AUTHOR, FILENAME) +); + +CREATE TABLE LIQUIBASECHANGELOGLOCK +( + ID int(11) NOT NULL, + LOCKED tinyint(1) NOT NULL, + LOCKGRANTED datetime, + LOCKEDBY varchar(255), + PRIMARY KEY (ID) +); \ No newline at end of file diff --git a/api-2.5/src/test/resources/log4j2.xml b/api-2.5/src/test/resources/log4j2.xml new file mode 100644 index 00000000..478cf73e --- /dev/null +++ b/api-2.5/src/test/resources/log4j2.xml @@ -0,0 +1,34 @@ + + + + + + + %p - %C{1}.%M(%L) |%d{ISO8601}| %m%n + + + + + + + + + + + + + + + + diff --git a/api-2.5/src/test/resources/testAppDataDir/configuration/liquibase/liquibase.xml b/api-2.5/src/test/resources/testAppDataDir/configuration/liquibase/liquibase.xml new file mode 100644 index 00000000..d745b27f --- /dev/null +++ b/api-2.5/src/test/resources/testAppDataDir/configuration/liquibase/liquibase.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/api-2.5/src/test/resources/testAppDataDir/configuration/liquibase/sql/test_changes_1.sql b/api-2.5/src/test/resources/testAppDataDir/configuration/liquibase/sql/test_changes_1.sql new file mode 100644 index 00000000..b16ce7d8 --- /dev/null +++ b/api-2.5/src/test/resources/testAppDataDir/configuration/liquibase/sql/test_changes_1.sql @@ -0,0 +1 @@ +insert into global_property (uuid, property, property_value) values (uuid(), 'test_changes_1', 'true'); \ No newline at end of file diff --git a/api-2.5/src/test/resources/testAppDataDir/configuration/liquibase/sql/test_changes_2.sql b/api-2.5/src/test/resources/testAppDataDir/configuration/liquibase/sql/test_changes_2.sql new file mode 100644 index 00000000..86ec7db6 --- /dev/null +++ b/api-2.5/src/test/resources/testAppDataDir/configuration/liquibase/sql/test_changes_2.sql @@ -0,0 +1 @@ +insert into global_property (uuid, property, property_value) values (uuid(), 'test_changes_2', 'true'); \ No newline at end of file diff --git a/api/src/main/java/org/openmrs/module/initializer/api/OrderedFile.java b/api/src/main/java/org/openmrs/module/initializer/api/OrderedFile.java index 4f542846..bb7381ab 100644 --- a/api/src/main/java/org/openmrs/module/initializer/api/OrderedFile.java +++ b/api/src/main/java/org/openmrs/module/initializer/api/OrderedFile.java @@ -24,7 +24,7 @@ public OrderedFile(String pathname) { order = fetchOrder(this); } catch (UnsupportedOperationException e) { - log.warn(e.getMessage()); + log.debug(e.getMessage()); } catch (Exception e) { log.error("There was an error while attempting to read the loading order of a configuration file: " diff --git a/api/src/main/java/org/openmrs/module/initializer/api/loaders/AddressHierarchyLoader.java b/api/src/main/java/org/openmrs/module/initializer/api/loaders/AddressHierarchyLoader.java index 7a7705c7..3e294fe0 100644 --- a/api/src/main/java/org/openmrs/module/initializer/api/loaders/AddressHierarchyLoader.java +++ b/api/src/main/java/org/openmrs/module/initializer/api/loaders/AddressHierarchyLoader.java @@ -29,7 +29,8 @@ public ConfigDirUtil getDirUtil() { public void loadUnsafe(List wildcardExclusions, boolean doThrow) throws Exception { try { - AddressConfigurationLoader.loadAddressConfiguration(Paths.get(iniz.getConfigDirPath()), Paths.get(iniz.getChecksumsDirPath())); + AddressConfigurationLoader.loadAddressConfiguration(Paths.get(iniz.getConfigDirPath()), + Paths.get(iniz.getChecksumsDirPath())); } catch (Exception e) { log.error(e.getMessage()); diff --git a/api/src/main/java/org/openmrs/module/initializer/api/loaders/LiquibaseLoader.java b/api/src/main/java/org/openmrs/module/initializer/api/loaders/LiquibaseLoader.java index b9b66503..5e9a1049 100644 --- a/api/src/main/java/org/openmrs/module/initializer/api/loaders/LiquibaseLoader.java +++ b/api/src/main/java/org/openmrs/module/initializer/api/loaders/LiquibaseLoader.java @@ -1,12 +1,13 @@ package org.openmrs.module.initializer.api.loaders; -import java.io.File; +import org.openmrs.annotation.OpenmrsProfile; import org.openmrs.module.initializer.Domain; import org.openmrs.module.initializer.api.ConfigDirUtil; import org.openmrs.util.DatabaseUpdater; -import org.springframework.stereotype.Component; -@Component +import java.io.File; + +@OpenmrsProfile(openmrsPlatformVersion = "2.1.1 - 2.5.4") public class LiquibaseLoader extends BaseFileLoader { public static final String LIQUIBASE_FILE_NAME = "liquibase"; diff --git a/omod/pom.xml b/omod/pom.xml index c1aa6a99..f9441c25 100644 --- a/omod/pom.xml +++ b/omod/pom.xml @@ -36,6 +36,12 @@ ${project.parent.artifactId}-api-2.4 ${project.parent.version} + + + ${project.parent.groupId} + ${project.parent.artifactId}-api-2.5 + ${project.parent.version} + ${project.parent.groupId} diff --git a/omod/src/main/resources/config.xml b/omod/src/main/resources/config.xml index 072737df..24e0e669 100644 --- a/omod/src/main/resources/config.xml +++ b/omod/src/main/resources/config.xml @@ -17,7 +17,11 @@ /lib/initializer-api-2.4-${project.version}.jar - 2.4.* - 2.* + 2.4.* - 9.* + + + /lib/initializer-api-2.5-${project.version}.jar + 2.5.* - 9.* diff --git a/pom.xml b/pom.xml index 0e077899..ec860eeb 100644 --- a/pom.xml +++ b/pom.xml @@ -36,6 +36,7 @@ api-2.2 api-2.3 api-2.4 + api-2.5 omod @@ -54,6 +55,7 @@ 2.2.0 2.3.6 2.4.0 + 2.5.5 ${openmrsVersion2.1} diff --git a/validator/pom.xml b/validator/pom.xml index 8ba3b202..324a224e 100644 --- a/validator/pom.xml +++ b/validator/pom.xml @@ -133,6 +133,13 @@ jar + + ${project.parent.groupId} + ${project.parent.artifactId}-api-2.5 + ${project.parent.version} + jar + + org.openmrs.api openmrs-api