UpgradeProductService.java
package org.exoplatform.commons.upgrade;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.commons.lang.StringUtils;
import org.picocontainer.Startable;
import org.exoplatform.commons.api.settings.SettingService;
import org.exoplatform.commons.api.settings.SettingValue;
import org.exoplatform.commons.api.settings.data.Context;
import org.exoplatform.commons.api.settings.data.Scope;
import org.exoplatform.commons.cluster.StartableClusterAware;
import org.exoplatform.commons.info.MissingProductInformationException;
import org.exoplatform.commons.info.ProductInformations;
import org.exoplatform.commons.utils.PropertyManager;
import org.exoplatform.container.ExoContainerContext;
import org.exoplatform.container.PortalContainer;
import org.exoplatform.container.component.RequestLifeCycle;
import org.exoplatform.container.xml.InitParams;
import org.exoplatform.services.jcr.ext.hierarchy.NodeHierarchyCreator;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
public class UpgradeProductService implements StartableClusterAware {
public static final Context UPGRADE_PRODUCT_CONTEXT = Context.GLOBAL.id("UPGRADE_PRODUCT_CONTEXT");
private static final Log LOG = ExoLogger.getLogger(UpgradeProductService.class);
private static final String UPGRADE_PLUGIN_VERSION_KEY = "UPGRADE_PLUGIN_VERSION";
private static final String PLUGINS_ORDER = "commons.upgrade.plugins.order";
private static final String PROCEED_UPGRADE_FIRST_RUN_KEY = "proceedUpgradeWhenFirstRun";
private static final String PRODUCT_VERSION_ZERO = "0";
private ExecutorService executorService = Executors.newCachedThreadPool();
private PortalContainer portalContainer;
private List<UpgradeProductPlugin> upgradePlugins = new ArrayList<UpgradeProductPlugin>();
private Set<UpgradeProductPlugin> allUpgradePlugins= new HashSet<UpgradeProductPlugin>();
private ProductInformations productInformations = null;
private SettingService settingService = null;
private NodeHierarchyCreator nodeHierarchyCreator = null;
private boolean proceedUpgradeFirstRun = false;
private Comparator<UpgradeProductPlugin> pluginsComparator = null;
/**
* Constructor with services and init params injected by Kernel
*
* @param portalContainer
* @param nodeHierarchyCreator
* @param settingService
* @param productInformations
* @param initParams
*/
public UpgradeProductService(PortalContainer portalContainer, NodeHierarchyCreator nodeHierarchyCreator, SettingService settingService, ProductInformations productInformations, InitParams initParams) {
this.productInformations = productInformations;
this.nodeHierarchyCreator = nodeHierarchyCreator;
this.settingService = settingService;
this.portalContainer = portalContainer;
if (!initParams.containsKey(PROCEED_UPGRADE_FIRST_RUN_KEY)) {
LOG.warn("init param '" + PROCEED_UPGRADE_FIRST_RUN_KEY + "' isn't set, use default value (" + proceedUpgradeFirstRun
+ "). Don't proceed upgrade when this service will run for the first time.");
} else {
proceedUpgradeFirstRun = Boolean.parseBoolean(initParams.getValueParam(PROCEED_UPGRADE_FIRST_RUN_KEY).getValue());
}
// Gets the execution order property: "commons.upgrade.plugins.order".
String pluginsOrder = PropertyManager.getProperty(PLUGINS_ORDER);
if (StringUtils.isBlank(pluginsOrder)) {
// Use plugin execution order of plugins if PLUGINS_ORDER parameter not set
LOG.info("Property '{}' wasn't set, use execution order of each plugin.", PLUGINS_ORDER);
pluginsComparator = new Comparator<UpgradeProductPlugin>() {
public int compare(UpgradeProductPlugin o1, UpgradeProductPlugin o2) {
int index1 = o1.getPluginExecutionOrder();
index1 = index1 <= 0 ? upgradePlugins.size() : index1;
int index2 = o2.getPluginExecutionOrder();
index2 = index2 <= 0 ? upgradePlugins.size() : index2;
return index1 - index2;
}
};
} else {
// If PLUGINS_ORDER parameter is set, use it and ignore plugin execution order
final List<String> pluginsOrderList = Arrays.asList(pluginsOrder.split(","));
pluginsComparator = new Comparator<UpgradeProductPlugin>() {
public int compare(UpgradeProductPlugin o1, UpgradeProductPlugin o2) {
int index1 = pluginsOrderList.indexOf(o1.getName());
index1 = index1 < 0 ? upgradePlugins.size() : index1;
int index2 = pluginsOrderList.indexOf(o2.getName());
index2 = index2 < 0 ? upgradePlugins.size() : index2;
return index1 - index2;
}
};
}
}
/**
* Method called by eXo Kernel to inject upgrade plugins
*
* @param upgradeProductPlugin
*/
public void addUpgradePlugin(UpgradeProductPlugin upgradeProductPlugin) {
// add only enabled plugins
if (upgradeProductPlugin.isEnabled()) {
if (upgradePlugins.contains(upgradeProductPlugin)) {
LOG.warn("Duplicated upgrade plugin '{}'.", upgradeProductPlugin.getName());
} else {
LOG.info("Add Product UpgradePlugin '{}'", upgradeProductPlugin.getName());
upgradePlugins.add(upgradeProductPlugin);
allUpgradePlugins.add(upgradeProductPlugin);
}
} else {
LOG.info("UpgradePlugin: name = '{}' is disabled.", upgradeProductPlugin.getName());
}
}
/**
* This method is called by eXo Kernel when starting the parent
* ExoContainer
*/
public void start() {
// Make sure that related services are started before starting this service
if (nodeHierarchyCreator instanceof Startable) {
((Startable) nodeHierarchyCreator).start();
}
productInformations.start();
if (productInformations.isFirstRun()) {
LOG.info("Proceed upgrade on first run = {}", proceedUpgradeFirstRun);
// If first run of upgrade API, and if disabled on first run, ignore plugins
if (!proceedUpgradeFirstRun) {
LOG.info("Ignore all upgrade plugins");
for (UpgradeProductPlugin upgradeProductPlugin : allUpgradePlugins) {
// Mark Plugin as executed to avoid that the plugin is executed future version upgrade
String currentProductPluginVersion = getCurrentVersion(upgradeProductPlugin);
UpgradePluginExecutionContext currenUpgradePluginExecutionContext = new UpgradePluginExecutionContext(currentProductPluginVersion, 0);
storeUpgradePluginVersion(upgradeProductPlugin, currenUpgradePluginExecutionContext);
}
return;
}
// If first run, set previous version to 0
productInformations.setPreviousVersionsIfFirstRun(PRODUCT_VERSION_ZERO);
}
// Sort the upgrade Plugins to use the execution order
Collections.sort(upgradePlugins, pluginsComparator);
LOG.info("Start transparent upgrade framework");
try {
// If the upgradePluginNames array contains less elements than
// the upgradePlugins list, execute these remaining plugins.
for (UpgradeProductPlugin upgradeProductPlugin : upgradePlugins) {
// Get stored version for this specific Upgrade Plugin from SettingService
UpgradePluginExecutionContext previousUpgradePluginExecutionContext = getPreviousUpgradePluginVersion(upgradeProductPlugin);
String previousUpgradePluginVersion = previousUpgradePluginExecutionContext == null ? null : previousUpgradePluginExecutionContext.getVersion();
// If the specific version is null, get it from GroupId an store it in SettingService
// The retrieval from GroupId will not be done if checkGroupIdVersion == null
String previousGroupVersion = getPreviousVersionByGroupId(upgradeProductPlugin);
String previousVersion = StringUtils.isBlank(previousUpgradePluginVersion) ? previousGroupVersion : previousUpgradePluginVersion;
// Get current running version
String currentVersion = getCurrentVersion(upgradeProductPlugin);
try {
// The plugin will determine if it should proceed to upgrade
if (upgradeProductPlugin.shouldProceedToUpgrade(currentVersion, previousGroupVersion, previousUpgradePluginExecutionContext)) {
// Store previous version for this specific plugin. This version will be updated to currentVersion
// only if proceedToUpgrade succeeds, else, the plugin will be executed again.
// In case of isExecuteOnlyOnce==true, we shouldn't store any information for the specific plugin,
// else it will not be executed if an error occurs
if (StringUtils.isBlank(previousUpgradePluginVersion)) {
previousUpgradePluginExecutionContext = new UpgradePluginExecutionContext(previousGroupVersion, 0);
storeUpgradePluginVersion(upgradeProductPlugin, previousUpgradePluginExecutionContext);
}
// Proceed to upgrade upgrade plugin
LOG.info("Proceed upgrade the plugin (async = {}): name = {} from version {} to {}",
upgradeProductPlugin.isAsyncUpgradeExecution(),
upgradeProductPlugin.getName(),
previousVersion,
currentVersion);
if (upgradeProductPlugin.isAsyncUpgradeExecution()) {
final UpgradePluginExecutionContext previousUpgradePluginExecutionContextFinal = previousUpgradePluginExecutionContext;
Runnable task = () -> {
ExoContainerContext.setCurrentContainer(portalContainer);
RequestLifeCycle.begin(portalContainer);
try {
proceedToUpgrade(upgradeProductPlugin, currentVersion, previousVersion, previousUpgradePluginExecutionContextFinal);
} finally {
RequestLifeCycle.end();
}
};
executorService.execute(task);
} else {
proceedToUpgrade(upgradeProductPlugin, currentVersion, previousVersion, previousUpgradePluginExecutionContext);
}
} else {
LOG.info("Ignore upgrade plugin {} from version {} to {}",
upgradeProductPlugin.getName(),
previousVersion,
currentVersion);
}
} catch (Exception e) {
LOG.error("Error while upgrading plugin with name '" + upgradeProductPlugin.getName()
+ "'. The upgrade plugin will attempt again next startup.", e);
}
}
productInformations.initProductInformation(productInformations.getProductInformationProperties());
productInformations.storeProductInformation(productInformations.getProductInformation());
} catch (Exception e) {
LOG.error("Error while executing upgrade plugins", e);
}
}
/**
* {@inheritDoc}
*/
public void stop() {
executorService.shutdown();
}
@Override
public boolean isDone() {
// Avoid start this service in other cluster nodes
return false;
}
/**
* Re-import all upgrade-plugins for service
*/
public void resetService()
{
//Reset product information
productInformations.start();
//Reload list Upgrade-Plugins
upgradePlugins.clear();
Iterator<UpgradeProductPlugin> iterator= allUpgradePlugins.iterator();
while(iterator.hasNext())
{
UpgradeProductPlugin upgradeProductPlugin= iterator.next();
upgradePlugins.add(upgradeProductPlugin);
}
}
private void proceedToUpgrade(UpgradeProductPlugin upgradeProductPlugin, String currentVersion, String previousVersion, UpgradePluginExecutionContext upgradePluginExecutionContext) {
upgradeProductPlugin.beforeUpgrade();
try {
upgradeProductPlugin.processUpgrade(previousVersion, currentVersion);
upgradePluginExecutionContext.setExecutionCount(upgradePluginExecutionContext.getExecutionCount() + 1);
upgradePluginExecutionContext.setVersion(currentVersion);
storeUpgradePluginVersion(upgradeProductPlugin, upgradePluginExecutionContext);
LOG.info("Upgrade of plugin {} completed.", upgradeProductPlugin.getName());
} catch (Exception e) {
LOG.error("Error while upgrading plugin with name '" + upgradeProductPlugin.getName()
+ "'. The upgrade plugin will attempt again next startup.", e);
} finally {
upgradeProductPlugin.afterUpgrade();
}
}
private String getCurrentVersion(UpgradeProductPlugin upgradeProductPlugin) {
String currentUpgradePluginVersion = null;
try {
currentUpgradePluginVersion = productInformations.getVersion(upgradeProductPlugin.getName());
} catch (MissingProductInformationException e) {
try {
currentUpgradePluginVersion = productInformations.getVersion(upgradeProductPlugin.getProductGroupId());
} catch (MissingProductInformationException e1) {
currentUpgradePluginVersion = PRODUCT_VERSION_ZERO;
}
}
return currentUpgradePluginVersion;
}
private String getPreviousVersionByGroupId(UpgradeProductPlugin upgradeProductPlugin) {
String previousUpgradePluginVersion;
try {
previousUpgradePluginVersion = productInformations.getPreviousVersion(upgradeProductPlugin.getName());
} catch (MissingProductInformationException e) {
try {
previousUpgradePluginVersion = productInformations.getPreviousVersion(upgradeProductPlugin.getProductGroupId());
} catch (MissingProductInformationException e1) {
previousUpgradePluginVersion = PRODUCT_VERSION_ZERO;
}
}
if (StringUtils.isBlank(previousUpgradePluginVersion)) {
previousUpgradePluginVersion = PRODUCT_VERSION_ZERO;
}
return previousUpgradePluginVersion;
}
private void storeUpgradePluginVersion(UpgradeProductPlugin upgradeProductPlugin, UpgradePluginExecutionContext upgradePluginExecution) {
if (upgradePluginExecution == null) {
throw new IllegalArgumentException("UpgradePluginExecution is null");
}
Scope upgradePluginScope = getUpgradePluginScope(upgradeProductPlugin);
settingService.set(UPGRADE_PRODUCT_CONTEXT, upgradePluginScope, UPGRADE_PLUGIN_VERSION_KEY, SettingValue.create(upgradePluginExecution.toString()));
}
private UpgradePluginExecutionContext getPreviousUpgradePluginVersion(UpgradeProductPlugin upgradeProductPlugin) {
Scope upgradePluginScope = getUpgradePluginScope(upgradeProductPlugin);
SettingValue<?> upgradePluginVersion = settingService.get(UPGRADE_PRODUCT_CONTEXT, upgradePluginScope, UPGRADE_PLUGIN_VERSION_KEY);
return upgradePluginVersion == null ? null : new UpgradePluginExecutionContext(upgradePluginVersion.getValue().toString());
}
private Scope getUpgradePluginScope(UpgradeProductPlugin upgradeProductPlugin) {
return Scope.APPLICATION.id(upgradeProductPlugin.getName());
}
}