Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions api/src/main/java/org/openmrs/api/AdministrationService.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@

import org.openmrs.GlobalProperty;
import org.openmrs.ImplementationId;
import org.openmrs.module.Module;
import org.openmrs.OpenmrsObject;
import org.openmrs.User;
import org.openmrs.annotation.Authorized;
import org.openmrs.api.db.AdministrationDAO;
import org.openmrs.util.DatabaseUpdateException;
import org.openmrs.util.HttpClient;
import org.openmrs.util.OpenmrsConstants;
import org.openmrs.util.PrivilegeConstants;
Expand Down Expand Up @@ -420,4 +422,33 @@ public interface AdministrationService extends OpenmrsService {
* <strong>Should</strong> return default common classes if no GPs defined
*/
List<String> getSerializerWhitelistTypes();

/**
* Checks whether a core setup needs to be run due to a version change.
*
* @return true if core setup should be executed because of a version change, false otherwise
*/
public boolean isCoreSetupOnVersionChangeNeeded();

/**
* Checks whether a module setup needs to be run due to a version change.
*
* @param moduleId the identifier of the module to check
* @return true if the module setup should be executed because of a version change, false otherwise
*/
public boolean isModuleSetupOnVersionChangeNeeded(String moduleId);

/**
* Executes the core setup procedures required after a core version change.
*
* @throws DatabaseUpdateException
*/
public void runCoreSetupOnVersionChange() throws DatabaseUpdateException;

/**
* Executes the setup procedures required for a module after a module version change.
*
* @param module the module for which the setup should be executed
*/
public void runModuleSetupOnVersionChange(Module module);
}
11 changes: 8 additions & 3 deletions api/src/main/java/org/openmrs/api/context/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -1311,13 +1311,18 @@ private static void checkForDatabaseUpdates(Properties props) throws DatabaseUpd

// this must be the first thing run in case it changes database mappings
if (updatesRequired) {
if (DatabaseUpdater.allowAutoUpdate()) {
DatabaseUpdater.executeChangelog();
} else {
if (!DatabaseUpdater.allowAutoUpdate()) {
throw new DatabaseUpdateException(
"Database updates are required. Call Context.updateDatabase() before .startup() to continue.");
}
}

if (getAdministrationService().isCoreSetupOnVersionChangeNeeded()) {
log.info("Detected core version change. Running core setup hooks and Liquibase.");
getAdministrationService().runCoreSetupOnVersionChange();
}

log.info("Database update check completed.");
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.SortedMap;
Expand Down Expand Up @@ -59,6 +60,8 @@
import org.openmrs.module.ModuleUtil;
import org.openmrs.obs.ComplexData;
import org.openmrs.person.PersonMergeLogData;
import org.openmrs.util.DatabaseUpdateException;
import org.openmrs.util.DatabaseUpdater;
import org.openmrs.util.HttpClient;
import org.openmrs.util.LocaleUtility;
import org.openmrs.util.OpenmrsConstants;
Expand Down Expand Up @@ -1010,4 +1013,99 @@ public List<Class<?>> getRefTypes() {
return Arrays.asList(GlobalProperty.class);
}

/**
* @see org.openmrs.api.AdministrationService#isCoreSetupOnVersionChangeNeeded()
*/
@Override
public boolean isCoreSetupOnVersionChangeNeeded() {
String stored = getStoredCoreVersion();
String current = OpenmrsConstants.OPENMRS_VERSION_SHORT;
boolean forceSetup = Boolean.parseBoolean(getGlobalProperty("force.setup", "false"));

return forceSetup || !Objects.equals(stored, current);
}

/**
* @see org.openmrs.api.AdministrationService#isModuleSetupOnVersionChangeNeeded(String)
*/
@Override
public boolean isModuleSetupOnVersionChangeNeeded(String moduleId) {
String stored = getStoredModuleVersion(moduleId);
Module module = ModuleFactory.getModuleById(moduleId);
if (module == null) {
return false;
}
String current = module.getVersion();
boolean forceSetup = Boolean.parseBoolean(getGlobalProperty("force.setup", "false"));

return forceSetup || !Objects.equals(stored, current);
}

/**
* @see org.openmrs.api.AdministrationService#runCoreSetupOnVersionChange()
*/
@Override
@Transactional
public void runCoreSetupOnVersionChange() throws DatabaseUpdateException {
DatabaseUpdater.executeChangelog();
storeCoreVersion();
}

/**
* @see org.openmrs.api.AdministrationService#runModuleSetupOnVersionChange(Module)
*/
@Override
@Transactional
public void runModuleSetupOnVersionChange(Module module) {
if (module == null) {
return;
}

String moduleId = module.getModuleId();
String prevCoreVersion = getStoredCoreVersion() != null ? getStoredCoreVersion() : OpenmrsConstants.OPENMRS_VERSION_SHORT;
String prevModuleVersion = getStoredModuleVersion(moduleId);

module.getModuleActivator().setupOnVersionChangeBeforeSchemaChanges(prevCoreVersion, prevModuleVersion);
ModuleFactory.runLiquibaseForModule(module);
module.getModuleActivator().setupOnVersionChange(prevCoreVersion, prevModuleVersion);

storeModuleVersion(moduleId, module.getVersion());
}

protected String getStoredCoreVersion() {
return getGlobalProperty("core.version");
}

protected String getStoredModuleVersion(String moduleId) {
return getGlobalProperty("module." + moduleId + ".version");
}

protected void storeCoreVersion() {
saveGlobalProperty("core.version", OpenmrsConstants.OPENMRS_VERSION_SHORT, "Saved the state of this core version for future restarts");
}

protected void storeModuleVersion(String moduleId, String version) {
String propertyName = "module." + moduleId + ".version";
saveGlobalProperty(propertyName, version, "Saved the state of this module version for future restarts");
}

/**
* Convenience method to save a global property with the given value. Proxy privileges are added so
* that this can occur at startup.
*/
protected void saveGlobalProperty(String key, String value, String desc) {
try {
GlobalProperty gp = getGlobalPropertyObject(key);
if (gp == null) {
gp = new GlobalProperty(key, value, desc);
} else {
gp.setPropertyValue(value);
}

saveGlobalProperty(gp);
}
catch (Exception e) {
log.warn("Unable to save the global property", e);
}
}
}
13 changes: 13 additions & 0 deletions api/src/main/java/org/openmrs/module/BaseModuleActivator.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,17 @@ public void willStart() {
public void willStop() {
}

/**
* @see org.openmrs.module.ModuleActivator#setupOnVersionChangeBeforeSchemaChanges(String, String)
*/
@Override
public void setupOnVersionChangeBeforeSchemaChanges(String previousCoreVersion, String previousModuleVersion) {
}

/**
* @see org.openmrs.module.ModuleActivator#setupOnVersionChange(String, String)
*/
@Override
public void setupOnVersionChange(String previousCoreVersion, String previousModuleVersion) {
}
}
10 changes: 10 additions & 0 deletions api/src/main/java/org/openmrs/module/ModuleActivator.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,14 @@ public interface ModuleActivator {
*/
public void stopped();

/**
* Called before Liquibase runs, but only if core or this module version changed.
*/
default void setupOnVersionChangeBeforeSchemaChanges(String previousCoreVersion, String previousModuleVersion) {}

/**
* Called after Liquibase runs, but only if core or this module version changed.
*/
default void setupOnVersionChange(String previousCoreVersion, String previousModuleVersion) {}

}
14 changes: 11 additions & 3 deletions api/src/main/java/org/openmrs/module/ModuleFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -694,9 +694,10 @@ public static Module startModuleInternal(Module module, boolean isOpenmrsStartup
Context.removeProxyPrivilege("");
}

// run module's optional liquibase.xml immediately after sqldiff.xml
log.debug("Run module liquibase: {}", module.getModuleId());
runLiquibase(module);
if (Context.getAdministrationService().isModuleSetupOnVersionChangeNeeded(module.getModuleId())) {
log.info("Detected version change for module {}. Running setup hooks and module Liquibase.", module.getModuleId());
Context.getAdministrationService().runModuleSetupOnVersionChange(module);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be much cleaner to put this logic inside a service method. It would run in a transaction this way so runInSingleTransaction would not be needed.


// effectively mark this module as started successfully
getStartedModulesMap().put(moduleId, module);
Expand Down Expand Up @@ -945,6 +946,13 @@ private static void runDiff(Module module, String version, String sql) {
}

}

/**
* This is a convenience method that exposes the private {@link #runLiquibase(Module)} method.
*/
public static void runLiquibaseForModule(Module module) {
runLiquibase(module);
}

/**
* Execute all not run changeSets in liquibase.xml for the given module
Expand Down
28 changes: 28 additions & 0 deletions api/src/test/java/org/openmrs/api/AdministrationServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -51,6 +52,8 @@
import org.openmrs.customdatatype.datatype.DateDatatype;
import org.openmrs.messagesource.MutableMessageSource;
import org.openmrs.messagesource.impl.MutableResourceBundleMessageSource;
import org.openmrs.module.Module;
import org.openmrs.module.ModuleActivator;
import org.openmrs.test.jupiter.BaseContextSensitiveTest;
import org.openmrs.util.HttpClient;
import org.openmrs.util.LocaleUtility;
Expand Down Expand Up @@ -1160,4 +1163,29 @@ public void getSerializerWhitelistTypes_shouldReturnDefaultCommonClassesIfNoGPS(
"hierarchyOf:org.openmrs.messagesource.PresentationMessage",
"hierarchyOf:org.openmrs.person.PersonMergeLogData"));
}

@Test
public void runModuleSetupOnVersionChange_shouldExecuteLiquibaseAndStoreNewVersion() {
// old version
adminService.setGlobalProperty("module.testmodule.version", "1.0.0");
assertEquals("1.0.0", adminService.getGlobalProperty("module.testmodule.version"));

String previousModuleVersion = "1.0.0";
String previousCoreVersion = OpenmrsConstants.OPENMRS_VERSION_SHORT;

Module module = new Module("Test Module");
module.setModuleId("testmodule");
module.setVersion("1.2.3");

ModuleActivator activator = mock(ModuleActivator.class);
module.setModuleActivator(activator);

adminService.runModuleSetupOnVersionChange(module);

assertEquals("1.2.3", adminService.getGlobalProperty("module.testmodule.version"));

// verify hook methods must be called
verify(activator).setupOnVersionChangeBeforeSchemaChanges(previousCoreVersion, previousModuleVersion);
verify(activator).setupOnVersionChange(previousCoreVersion, previousModuleVersion);
}
}
56 changes: 56 additions & 0 deletions api/src/test/java/org/openmrs/module/ModuleTestData.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ public class ModuleTestData {

private Map<String, Integer> stoppedCallCount = new HashMap<>();

private Map<String, Integer> setupOnVersionChangeBeforeSchemaChangesCallCount = new HashMap<>();

private Map<String, Integer> setupOnVersionChangeCallCount = new HashMap<>();

private Map<String, Long> willRefreshContextCallTime = new HashMap<>();

private Map<String, Long> contextRefreshedCallTime = new HashMap<>();
Expand All @@ -39,6 +43,10 @@ public class ModuleTestData {

private Map<String, Long> stoppedCallTime = new HashMap<>();

private Map<String, Long> setupOnVersionChangeBeforeSchemaChangesCallTime = new HashMap<>();

private Map<String, Long> setupOnVersionChangeCallTime = new HashMap<>();

private ModuleTestData() {

}
Expand All @@ -59,13 +67,17 @@ public synchronized void init(String moduleId) {
startedCallCount.put(moduleId, 0);
willStopCallCount.put(moduleId, 0);
stoppedCallCount.put(moduleId, 0);
setupOnVersionChangeBeforeSchemaChangesCallCount.put(moduleId, 0);
setupOnVersionChangeCallCount.put(moduleId, 0);

willRefreshContextCallTime.put(moduleId, 0L);
contextRefreshedCallTime.put(moduleId, 0L);
willStartCallTime.put(moduleId, 0L);
startedCallTime.put(moduleId, 0L);
willStopCallTime.put(moduleId, 0L);
stoppedCallTime.put(moduleId, 0L);
setupOnVersionChangeBeforeSchemaChangesCallTime.put(moduleId, 0L);
setupOnVersionChangeCallTime.put(moduleId, 0L);
}

public synchronized Integer getWillRefreshContextCallCount(String moduleId) {
Expand Down Expand Up @@ -116,6 +128,22 @@ public synchronized Integer getStoppedCallCount(String moduleId) {
return count;
}

public synchronized Integer getSetupOnVersionChangeBeforeSchemaChangesCallCount(String moduleId) {
Integer count = setupOnVersionChangeBeforeSchemaChangesCallCount.get(moduleId);
if (count == null) {
count = 0;
}
return count;
}

public synchronized Integer getSetupOnVersionChangeCallCount(String moduleId) {
Integer count = setupOnVersionChangeCallCount.get(moduleId);
if (count == null) {
count = 0;
}
return count;
}

public synchronized void willRefreshContext(String moduleId) {
willRefreshContextCallTime.put(moduleId, new Date().getTime());

Expand Down Expand Up @@ -176,6 +204,26 @@ public synchronized void stopped(String moduleId) {
stoppedCallCount.put(moduleId, count + 1);
}

public synchronized void setupOnVersionChangeBeforeSchemaChanges(String moduleId) {
setupOnVersionChangeBeforeSchemaChangesCallTime.put(moduleId, new Date().getTime());

Integer count = setupOnVersionChangeBeforeSchemaChangesCallCount.get(moduleId);
if (count == null) {
count = 0;
}
setupOnVersionChangeBeforeSchemaChangesCallCount.put(moduleId, count + 1);
}

public synchronized void setupOnVersionChange(String moduleId) {
setupOnVersionChangeCallTime.put(moduleId, new Date().getTime());

Integer count = setupOnVersionChangeCallCount.get(moduleId);
if (count == null) {
count = 0;
}
setupOnVersionChangeCallCount.put(moduleId, count + 1);
}

public synchronized Long getWillRefreshContextCallTime(String moduleId) {
return willRefreshContextCallTime.get(moduleId);
}
Expand All @@ -199,4 +247,12 @@ public synchronized Long getWillStopCallTime(String moduleId) {
public synchronized Long getStoppedCallTime(String moduleId) {
return stoppedCallTime.get(moduleId);
}

public synchronized Long getSetupOnVersionChangeBeforeSchemaChangesCallTime(String moduleId) {
return setupOnVersionChangeBeforeSchemaChangesCallTime.get(moduleId);
}

public synchronized Long getSetupOnVersionChangeCallTime(String moduleId) {
return setupOnVersionChangeCallTime.get(moduleId);
}
}
Loading