Skip to content

Commit aba1d9b

Browse files
TRUNK-6418: Run liquibase checks and data imports only when version of core or modules changes
1 parent fb86453 commit aba1d9b

File tree

8 files changed

+304
-4
lines changed

8 files changed

+304
-4
lines changed

api/src/main/java/org/openmrs/module/BaseModuleActivator.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,17 @@ public void willStart() {
6161
public void willStop() {
6262
}
6363

64+
/**
65+
* @see org.openmrs.module.ModuleActivator#setupOnVersionChangeBeforeSchemaChanges(String, String)
66+
*/
67+
@Override
68+
public void setupOnVersionChangeBeforeSchemaChanges(String previousCoreVersion, String previousModuleVersion) {
69+
}
70+
71+
/**
72+
* @see org.openmrs.module.ModuleActivator#setupOnVersionChange(String, String)
73+
*/
74+
@Override
75+
public void setupOnVersionChange(String previousCoreVersion, String previousModuleVersion) {
76+
}
6477
}

api/src/main/java/org/openmrs/module/ModuleActivator.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,14 @@ public interface ModuleActivator {
5656
*/
5757
public void stopped();
5858

59+
/**
60+
* Called before Liquibase runs, but only if core or this module version changed.
61+
*/
62+
default void setupOnVersionChangeBeforeSchemaChanges(String previousCoreVersion, String previousModuleVersion) {}
63+
64+
/**
65+
* Called after Liquibase runs, but only if core or this module version changed.
66+
*/
67+
default void setupOnVersionChange(String previousCoreVersion, String previousModuleVersion) {}
68+
5969
}

api/src/main/java/org/openmrs/module/ModuleFactory.java

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.List;
2626
import java.util.Map;
2727
import java.util.Map.Entry;
28+
import java.util.Objects;
2829
import java.util.Set;
2930
import java.util.SortedMap;
3031
import java.util.concurrent.ConcurrentHashMap;
@@ -52,16 +53,24 @@
5253
import org.slf4j.Logger;
5354
import org.slf4j.LoggerFactory;
5455
import org.springframework.aop.Advisor;
56+
import org.springframework.beans.factory.annotation.Autowired;
5557
import org.springframework.context.support.AbstractRefreshableApplicationContext;
58+
import org.springframework.stereotype.Component;
59+
import org.springframework.transaction.PlatformTransactionManager;
60+
import org.springframework.transaction.TransactionManager;
61+
import org.springframework.transaction.support.TransactionTemplate;
5662
import org.springframework.util.StringUtils;
5763

5864
import liquibase.Contexts;
5965

6066
/**
6167
* Methods for loading, starting, stopping, and storing OpenMRS modules
6268
*/
69+
@Component
6370
public class ModuleFactory {
6471

72+
private static TransactionManager txManager;
73+
6574
private ModuleFactory() {
6675
}
6776

@@ -88,6 +97,11 @@ private ModuleFactory() {
8897

8998
private static final Set<String> actualStartupOrder = new LinkedHashSet<>();
9099

100+
@Autowired
101+
public void setTransactionManager(TransactionManager transactionManager) {
102+
ModuleFactory.txManager = transactionManager;
103+
}
104+
91105
/**
92106
* Add a module (in the form of a jar file) to the list of openmrs modules Returns null if an error
93107
* occurred and/or module was not successfully loaded
@@ -272,6 +286,7 @@ public static void startModules() {
272286
notifySuperUsersAboutModuleFailure(mod);
273287
}
274288
}
289+
storeCoreVersion();
275290
}
276291
}
277292

@@ -694,9 +709,32 @@ public static Module startModuleInternal(Module module, boolean isOpenmrsStartup
694709
Context.removeProxyPrivilege("");
695710
}
696711

697-
// run module's optional liquibase.xml immediately after sqldiff.xml
698-
log.debug("Run module liquibase: {}", module.getModuleId());
699-
runLiquibase(module);
712+
// Run version-change hooks + liquibase if necessary
713+
String prevCoreVersion = getStoredCoreVersion() != null ? getStoredCoreVersion() : OpenmrsConstants.OPENMRS_VERSION_SHORT;
714+
String currentCoreVersion = OpenmrsConstants.OPENMRS_VERSION_SHORT;
715+
716+
boolean forceSetup = Boolean.parseBoolean(
717+
Context.getAdministrationService().getGlobalProperty("force.setup", "false"));
718+
719+
String prevModuleVersion = getStoredModuleVersion(module);
720+
String currentModuleVersion = module.getVersion();
721+
722+
boolean versionChanged = forceSetup
723+
|| versionChanged(prevCoreVersion, currentCoreVersion, prevModuleVersion, currentModuleVersion);
724+
725+
if (versionChanged) {
726+
runInSingleTransaction(() -> {
727+
module.getModuleActivator().setupOnVersionChangeBeforeSchemaChanges(prevCoreVersion, prevModuleVersion);
728+
729+
// run module's optional liquibase.xml immediately after sqldiff.xml
730+
log.debug("Run module liquibase: {}", module.getModuleId());
731+
runLiquibase(module);
732+
733+
module.getModuleActivator().setupOnVersionChange(prevCoreVersion, prevModuleVersion);
734+
});
735+
736+
storeModuleVersion(module.getModuleId(), currentModuleVersion);
737+
}
700738

701739
// effectively mark this module as started successfully
702740
getStartedModulesMap().put(moduleId, module);
@@ -1624,4 +1662,58 @@ public static List<String> getDependencies(String moduleId) {
16241662
}
16251663
return dependentModules;
16261664
}
1665+
1666+
private static boolean versionChanged(String prevCore, String currentCore, String prevModule, String currentModule) {
1667+
return !Objects.equals(prevCore, currentCore) || !Objects.equals(prevModule, currentModule);
1668+
}
1669+
1670+
protected static String getStoredCoreVersion() {
1671+
return Context.getAdministrationService().getGlobalProperty("core.version");
1672+
}
1673+
1674+
protected static String getStoredModuleVersion(Module mod) {
1675+
return Context.getAdministrationService().getGlobalProperty("module." + mod.getModuleId() + ".version");
1676+
}
1677+
1678+
protected static void storeCoreVersion() {
1679+
try {
1680+
Context.addProxyPrivilege(PrivilegeConstants.MANAGE_GLOBAL_PROPERTIES);
1681+
1682+
Context.getAdministrationService().setGlobalProperty("core.version", OpenmrsConstants.OPENMRS_VERSION_SHORT);
1683+
} finally {
1684+
Context.removeProxyPrivilege(PrivilegeConstants.MANAGE_GLOBAL_PROPERTIES);
1685+
}
1686+
}
1687+
1688+
protected static void storeModuleVersion(String moduleId, String version) {
1689+
// Clear session so that the first-level cache is empty
1690+
Context.flushSession();
1691+
Context.clearSession();
1692+
try {
1693+
Context.addProxyPrivilege(PrivilegeConstants.MANAGE_GLOBAL_PROPERTIES);
1694+
String propertyName = "module." + moduleId + ".version";
1695+
1696+
AdministrationService adminService = Context.getAdministrationService();
1697+
GlobalProperty gp = adminService.getGlobalPropertyObject(propertyName);
1698+
1699+
if (gp == null) {
1700+
gp = new GlobalProperty(propertyName, version);
1701+
} else {
1702+
gp.setPropertyValue(version);
1703+
}
1704+
1705+
adminService.saveGlobalProperty(gp);
1706+
1707+
} finally {
1708+
Context.removeProxyPrivilege(PrivilegeConstants.MANAGE_GLOBAL_PROPERTIES);
1709+
}
1710+
}
1711+
1712+
private static void runInSingleTransaction(Runnable action) {
1713+
TransactionTemplate txTemplate = new TransactionTemplate((PlatformTransactionManager) txManager);
1714+
txTemplate.execute(status -> {
1715+
action.run();
1716+
return null;
1717+
});
1718+
}
16271719
}

api/src/test/java/org/openmrs/module/BaseModuleActivatorTest.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ public abstract class BaseModuleActivatorTest extends BaseContextSensitiveTest {
2626
protected static final String MODULE4_ID = "test4";
2727

2828
protected static final String MODULE5_ID = "test5";
29+
30+
protected static final String MODULE6_ID = "test6";
2931

3032
protected ModuleTestData moduleTestData;
3133

@@ -39,7 +41,8 @@ public void beforeEachTest() {
3941

4042
String modulesToLoad = "org/openmrs/module/include/test3-1.0-SNAPSHOT.omod "
4143
+ "org/openmrs/module/include/test1-1.0-SNAPSHOT.omod org/openmrs/module/include/test2-1.0-SNAPSHOT.omod "
42-
+ "org/openmrs/module/include/test4-1.0-SNAPSHOT.omod org/openmrs/module/include/test5-1.0-SNAPSHOT.omod";
44+
+ "org/openmrs/module/include/test4-1.0-SNAPSHOT.omod org/openmrs/module/include/test5-1.0-SNAPSHOT.omod "
45+
+ "org/openmrs/module/include/test6-1.0-SNAPSHOT.omod";
4346
runtimeProperties.setProperty(ModuleConstants.RUNTIMEPROPERTY_MODULE_LIST_TO_LOAD, modulesToLoad);
4447
ModuleUtil.startup(runtimeProperties);
4548
}
@@ -50,5 +53,6 @@ protected void init() {
5053
moduleTestData.init(MODULE3_ID);
5154
moduleTestData.init(MODULE4_ID);
5255
moduleTestData.init(MODULE5_ID);
56+
moduleTestData.init(MODULE6_ID);
5357
}
5458
}

api/src/test/java/org/openmrs/module/ModuleActivatorTest.java

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@
1111

1212
import static org.hamcrest.MatcherAssert.assertThat;
1313
import static org.hamcrest.Matchers.lessThanOrEqualTo;
14+
import static org.junit.jupiter.api.Assertions.assertEquals;
1415
import static org.junit.jupiter.api.Assertions.assertTrue;
1516

1617
import org.junit.jupiter.api.AfterAll;
1718
import org.junit.jupiter.api.Test;
19+
import org.openmrs.GlobalProperty;
20+
import org.openmrs.api.context.Context;
21+
import org.springframework.test.context.transaction.TestTransaction;
1822

1923
/**
2024
* Tests methods of the module activator that do not require refreshing of the spring application
@@ -60,6 +64,114 @@ public void shouldStartModulesInOrder() {
6064
assertTrue(moduleTestData.getStartedCallTime(MODULE1_ID) <= moduleTestData.getStartedCallTime(MODULE2_ID));
6165
assertTrue(moduleTestData.getStartedCallTime(MODULE2_ID) <= moduleTestData.getStartedCallTime(MODULE3_ID));
6266
}
67+
68+
@Test
69+
public void shouldRunSetupHooksOnInitialStartup() {
70+
TestTransaction.end();
71+
Module module = ModuleFactory.getModuleById(MODULE6_ID);
72+
moduleTestData.init(MODULE6_ID);
73+
74+
ModuleFactory.stopModule(module);
75+
76+
// delete previous module version for test
77+
GlobalProperty gp = Context.getAdministrationService().getGlobalPropertyObject("module." + MODULE6_ID + ".version");
78+
if (gp != null) {
79+
Context.getAdministrationService().purgeGlobalProperty(gp);
80+
}
81+
82+
ModuleFactory.startModule(module);
83+
84+
// Hooks should run because previous module version == null
85+
assertEquals(1, moduleTestData.getSetupOnVersionChangeBeforeSchemaChangesCallCount(MODULE6_ID));
86+
assertEquals(1, moduleTestData.getSetupOnVersionChangeCallCount(MODULE6_ID));
87+
}
88+
89+
@Test
90+
public void shouldNotRunSetupHooksWhenNoVersionChanged() {
91+
Module module = ModuleFactory.getModuleById(MODULE6_ID);
92+
moduleTestData.init(MODULE6_ID);
93+
94+
ModuleFactory.stopModule(module);
95+
ModuleFactory.startModule(module);
96+
97+
// Hooks should not run because no version change
98+
assertEquals(0, moduleTestData.getSetupOnVersionChangeBeforeSchemaChangesCallCount(MODULE6_ID));
99+
assertEquals(0, moduleTestData.getSetupOnVersionChangeCallCount(MODULE6_ID));
100+
}
101+
102+
@Test
103+
public void shouldRunSetupHooksWhenModuleVersionChanged() {
104+
Module module = ModuleFactory.getModuleById(MODULE6_ID);
105+
moduleTestData.init(MODULE6_ID);
106+
107+
ModuleFactory.stopModule(module);
108+
module.setVersion("1.3.0");
109+
ModuleFactory.startModule(module);
110+
111+
// Hooks should run because module version has changed
112+
assertEquals(1, moduleTestData.getSetupOnVersionChangeBeforeSchemaChangesCallCount(MODULE6_ID));
113+
assertEquals(1, moduleTestData.getSetupOnVersionChangeCallCount(MODULE6_ID));
114+
}
115+
116+
@Test
117+
public void shouldRunSetupHooksWhenCoreVersionChanged() {
118+
TestTransaction.end();
119+
Module module = ModuleFactory.getModuleById(MODULE6_ID);
120+
moduleTestData.init(MODULE6_ID);
121+
122+
ModuleFactory.stopModule(module);
123+
124+
// manually change previous core version for test
125+
String propertyName = "core.version";
126+
String value = "2.0.0";
127+
GlobalProperty gp = Context.getAdministrationService().getGlobalPropertyObject(propertyName);
128+
129+
if (gp == null) {
130+
gp = new GlobalProperty(propertyName, value);
131+
} else {
132+
gp.setPropertyValue(value);
133+
}
134+
Context.getAdministrationService().saveGlobalProperty(gp);
135+
136+
ModuleFactory.startModule(module);
137+
138+
// Hooks should run because core version has changed
139+
assertEquals(1, moduleTestData.getSetupOnVersionChangeBeforeSchemaChangesCallCount(MODULE6_ID));
140+
assertEquals(1, moduleTestData.getSetupOnVersionChangeCallCount(MODULE6_ID));
141+
142+
// clean up
143+
Context.getAdministrationService().purgeGlobalProperty(gp);
144+
}
145+
146+
@Test
147+
public void shouldRunSetupHooksWhenForceSetupGPIsTrue() {
148+
TestTransaction.end();
149+
Module module = ModuleFactory.getModuleById(MODULE6_ID);
150+
moduleTestData.init(MODULE6_ID);
151+
152+
ModuleFactory.stopModule(module);
153+
154+
// change force setup GP
155+
String propertyName = "force.setup";
156+
String value = "true";
157+
GlobalProperty gp = Context.getAdministrationService().getGlobalPropertyObject(propertyName);
158+
159+
if (gp == null) {
160+
gp = new GlobalProperty(propertyName, value);
161+
} else {
162+
gp.setPropertyValue(value);
163+
}
164+
Context.getAdministrationService().saveGlobalProperty(gp);
165+
166+
ModuleFactory.startModule(module);
167+
168+
// Hooks should run because force setup GP is true
169+
assertEquals(1, moduleTestData.getSetupOnVersionChangeBeforeSchemaChangesCallCount(MODULE6_ID));
170+
assertEquals(1, moduleTestData.getSetupOnVersionChangeCallCount(MODULE6_ID));
171+
172+
// clean up
173+
Context.getAdministrationService().purgeGlobalProperty(gp);
174+
}
63175

64176
@Test
65177
public void shouldCallWillStopAndStoppedOnlyForStoppedModule() {

0 commit comments

Comments
 (0)