Skip to content
Merged
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
35 changes: 35 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,37 @@ 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.
*
* @since 2.9.0
* @return true if core setup should be executed because of a version change, false otherwise
*/
boolean isCoreSetupOnVersionChangeNeeded();

/**
* Checks whether a module setup needs to be run due to a version change.
*
* @since 2.9.0
* @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
*/
boolean isModuleSetupOnVersionChangeNeeded(String moduleId);

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

/**
* Executes the setup procedures required for a module after a module version change.
*
* @since 2.9.0
* @param module the module for which the setup should be executed
*/
void runModuleSetupOnVersionChange(Module module);
}
43 changes: 6 additions & 37 deletions api/src/main/java/org/openmrs/api/context/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -1009,20 +1009,22 @@ public static synchronized void startup(Properties props) throws DatabaseUpdateE
// do any context database specific startup
getContextDAO().startup(props);

// find/set/check whether the current database version is compatible
checkForDatabaseUpdates(props);
if (getAdministrationService().isCoreSetupOnVersionChangeNeeded()) {
log.info("Detected core version change. Running core setup hooks and Liquibase.");
getAdministrationService().runCoreSetupOnVersionChange();
}

// this should be first in the startup routines so that the application
// data directory can be set from the runtime properties
OpenmrsUtil.startup(props);

openSession();
clearSession();

// add any privileges/roles that /must/ exist for openmrs to work
// correctly.
checkCoreDataset();

getContextDAO().setupSearchIndex();

// Loop over each module and startup each with these custom properties
Expand Down Expand Up @@ -1282,39 +1284,6 @@ public static void checkCoreDataset() {
OpenmrsConstants.GP_ALLERGEN_OTHER_NON_CODED_UUID));
}

/**
* Runs any needed updates on the current database if the user has the allow_auto_update runtime
* property set to true. If not set to true, then {@link #updateDatabase(Map)} must be called.<br>
* <br>
* If an {@link InputRequiredException} is thrown, a call to {@link #updateDatabase(Map)} is
* required with a mapping from question prompt to user answer.
*
* @param props the runtime properties
* @throws InputRequiredException if the {@link DatabaseUpdater} has determined that updates
* cannot continue without input from the user
* @see InputRequiredException#getRequiredInput() InputRequiredException#getRequiredInput() for
* the required question/datatypes
*/
private static void checkForDatabaseUpdates(Properties props) throws DatabaseUpdateException, InputRequiredException {
boolean updatesRequired;
try {
updatesRequired = DatabaseUpdater.updatesRequired();
}
catch (Exception e) {
throw new DatabaseUpdateException("Unable to check if database updates are required", e);
}

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

/**
* Updates the openmrs database to the latest. This is only needed if using the API alone. <br>
* <br>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,16 @@ public GlobalProperty getGlobalPropertyObject(String propertyName) {

query.where(condition);

return session.createQuery(query).uniqueResult();
} else {
return session.get(GlobalProperty.class, propertyName);
GlobalProperty gp = session.createQuery(query).uniqueResult();
if (gp != null) {
// GP may be null, but the session may contain an unflushed gp so
// we will do a final check with session.get below. It may happen,
// if flush is set to manual and running in a larger transaction.
return gp;
Copy link
Member Author

Choose a reason for hiding this comment

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

One test annotated with @StartModule was failing because of this. In general I think we have an issue with case-insensitive primary keys here. Session.get is using an L1 cache that is always case sensitive and we may run into strange errors, because of how we handle GPs. I will open an issue for that with a failing test case.

}
}

return session.get(GlobalProperty.class, propertyName);
}

@Override
Expand Down
121 changes: 121 additions & 0 deletions api/src/main/java/org/openmrs/api/impl/AdministrationServiceImpl.java
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 @@ -58,6 +59,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 @@ -982,4 +985,122 @@ public static List<Class<?>> getSerializerDefaultWhitelistHierarchyTypes() {
PersonMergeLogData.class);
return types;
}

/**
* @see org.openmrs.api.AdministrationService#isCoreSetupOnVersionChangeNeeded()
*/
@Override
public boolean isCoreSetupOnVersionChangeNeeded() {
boolean forceSetup = Boolean.parseBoolean(Context.getRuntimeProperties().getProperty("force.setup", "false"));
if (forceSetup) {
return true;
}

String stored = getStoredCoreVersion();
String current = OpenmrsConstants.OPENMRS_VERSION_SHORT;
return !Objects.equals(stored, current);
}

/**
* @see org.openmrs.api.AdministrationService#isModuleSetupOnVersionChangeNeeded(String)
*/
@Override
public boolean isModuleSetupOnVersionChangeNeeded(String moduleId) {
Module module = ModuleFactory.getModuleById(moduleId);
if (module == null) {
log.info("{} module is no longer installed, skipping setup", moduleId);
return false;
}

if (isCoreSetupOnVersionChangeNeeded()) {
return true;
}

String stored = getStoredModuleVersion(moduleId);
String current = module.getVersion();
if (!Objects.equals(stored, current)) {
return true;
} else {
log.info("{} module did not change, skipping setup", moduleId);
return false;
}
}

/**
* @see org.openmrs.api.AdministrationService#runCoreSetupOnVersionChange()
*/
@Override
@Transactional
public void runCoreSetupOnVersionChange() throws DatabaseUpdateException {
if (!ModuleFactory.getLoadedModules().isEmpty()) {
String prevCoreVersion = getStoredCoreVersion();

for (Module module : ModuleFactory.getLoadedModules()) {
String prevModuleVersion = getStoredModuleVersion(module.getModuleId());

module.getModuleActivator().setupOnVersionChangeBeforeSchemaChanges(prevCoreVersion, prevModuleVersion);
}
}

boolean updatesRequired;
try {
updatesRequired = DatabaseUpdater.updatesRequired();
}
catch (Exception e) {
throw new DatabaseUpdateException("Unable to check if database updates are required", e);
}

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

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();
String prevModuleVersion = getStoredModuleVersion(moduleId);

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

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

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

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

protected void storeCoreVersion() {
String propertyName = "core.version";
GlobalProperty gp = new GlobalProperty(propertyName, OpenmrsConstants.OPENMRS_VERSION_SHORT,
"Saved core version for future restarts");
dao.saveGlobalProperty(gp);
}

protected void storeModuleVersion(String moduleId, String version) {
String propertyName = "module." + moduleId + ".version";
GlobalProperty gp = new GlobalProperty(propertyName, version, "Saved module version for future restarts");
dao.saveGlobalProperty(gp);
}
}
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) {
}
}
18 changes: 18 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,22 @@ public interface ModuleActivator {
*/
public void stopped();

/**
* Called before Liquibase runs, but only if core or this module version changed.
*
* @param previousCoreVersion previous core version or <code>null</code> if first install
* @param previousModuleVersion previous module version or <code>null</code> if first install
* @since 2.9.0
*/
default void setupOnVersionChangeBeforeSchemaChanges(String previousCoreVersion, String previousModuleVersion) {}

/**
* Called after Liquibase runs, but only if core or this module version changed.
*
* @param previousCoreVersion previous core version or <code>null</code> if first install
* @param previousModuleVersion previous module version or <code>null</code> if first install
* @since 2.9.0
*/
default void setupOnVersionChange(String previousCoreVersion, String previousModuleVersion) {}

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

// run module's optional liquibase.xml immediately after sqldiff.xml
runLiquibase(module);
if (Context.getAdministrationService().isModuleSetupOnVersionChangeNeeded(module.getModuleId())) {
log.info("Module {} changed, running setup.", module.getModuleId());
Context.getAdministrationService().runModuleSetupOnVersionChange(module);
}

// effectively mark this module as started successfully
getStartedModulesMap().put(moduleId, module);
Expand Down Expand Up @@ -750,7 +752,7 @@ public static Module startModuleInternal(Module module, boolean isOpenmrsStartup
module.clearStartupError();
}
catch (Exception e) {
log.warn("Error while trying to start module: " + moduleId, e);
log.error("Error while trying to start module: {}", moduleId, e);
module.setStartupErrorMessage("Error while trying to start module", e);
notifySuperUsersAboutModuleFailure(module);
// undo all of the actions in startup
Expand All @@ -766,7 +768,7 @@ public static Module startModuleInternal(Module module, boolean isOpenmrsStartup
catch (Exception e2) {
// this will probably occur about the same place as the
// error in startup
log.debug("Error while stopping module: " + moduleId, e2);
log.debug("Error while stopping module: {}", moduleId, e2);
}
}

Expand Down Expand Up @@ -939,6 +941,14 @@ private static void runDiff(Module module, String version, String sql) {
}

}

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

/**
* Execute all not run changeSets in liquibase.xml for the given module
Expand Down
Loading
Loading