/* * *** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is part of dcm4che, an implementation of DICOM(TM) in * Java(TM), hosted at https://github.com/gunterze/dcm4che. * * The Initial Developer of the Original Code is * Agfa Healthcare. * Portions created by the Initial Developer are Copyright (C) 2015 * the Initial Developer. All Rights Reserved. * * Contributor(s): * See @authors listed below * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ package org.dcm4chee.conf.upgrade; import org.dcm4che3.conf.api.internal.DicomConfigurationManager; import org.dcm4che3.conf.api.upgrade.*; import org.dcm4che3.conf.core.api.ConfigurationUpgradeException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Method; import java.util.*; import java.util.stream.Collectors; /** * @author Roman K */ @SuppressWarnings("unchecked") public class UpgradeRunner { private static Logger log = LoggerFactory.getLogger(UpgradeRunner.class); private Collection<UpgradeScript> availableUpgradeScripts; private DicomConfigurationManager dicomConfigurationManager; private UpgradeSettings upgradeSettings; public UpgradeRunner(Collection<UpgradeScript> availableUpgradeScripts, DicomConfigurationManager dicomConfigurationManager, UpgradeSettings upgradeSettings) { this.availableUpgradeScripts = availableUpgradeScripts; this.dicomConfigurationManager = dicomConfigurationManager; this.upgradeSettings = upgradeSettings; } public void upgrade() { dicomConfigurationManager.runBatch(() -> { try { final String toVersion = upgradeSettings.getUpgradeToVersion(); ConfigurationMetadata configMetadata = loadOrInitConfigMetadata(); if (configMetadata.getVersion() == null) configMetadata.setVersion(UpgradeScript.NO_VERSION); String fromVersion = configMetadata.getVersion(); log.info("Dcm4che configuration init: upgrading configuration from version '{}' to version '{}'", fromVersion, toVersion); log.info("Config upgrade scripts specified in settings: {}", upgradeSettings.getUpgradeScriptsToRun()); log.info("Config upgrade scripts discovered in the deployment: {}", availableUpgradeScripts); SortedSet<UpgradeStep> upgradeSteps = collectUpgradeSteps(configMetadata); for (UpgradeStep upgradeStep : upgradeSteps) { try { log.info(upgradeStep.label); upgradeStep.action.run(); } catch (RuntimeException e) { throw new ConfigurationUpgradeException("Error while running upgrade step " + upgradeStep.label, e); } } // update last executed version for all enabled scripts getOrderedScriptsToRun().forEach((s) -> { getUpgradeScriptMetadata(configMetadata, getScriptName(s)) .setLastVersionExecuted(getScriptVersion(s).toString()); }); // update version configMetadata.setVersion(toVersion); // persist updated metadata dicomConfigurationManager .getTypeSafeConfiguration() .save(DicomConfigurationManager.METADATA_ROOT_PATH, configMetadata, ConfigurationMetadata.class); } catch (RuntimeException e) { throw new ConfigurationUpgradeException("Error while running the configuration upgrade", e); } }); log.info("Configuration upgrade completed successfully"); } private List<UpgradeScript> getOrderedScriptsToRun() { return upgradeSettings.getUpgradeScriptsToRun().stream() .map((upgradeScriptName) -> { for (UpgradeScript script : availableUpgradeScripts) { if (getScriptName(script).equals(upgradeScriptName)) { return Optional.of(script); } } if (upgradeSettings.isIgnoreMissingUpgradeScripts()) { log.warn("Missing upgrade script '" + upgradeScriptName + "' ignored! DISABLE 'IgnoreMissingUpgradeScripts' SETTING FOR PRODUCTION ENVIRONMENT."); } else { throw new ConfigurationUpgradeException("Upgrade script '" + upgradeScriptName + "' not found in the deployment"); } // return Optional.empty() won't work due to generics... //noinspection ConstantConditions return Optional.ofNullable((UpgradeScript) null); } ) .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toList()); } private SortedSet<UpgradeStep> collectUpgradeSteps(ConfigurationMetadata configMetadata) { Properties props = new Properties(); props.putAll(upgradeSettings.getProperties()); TreeSet<UpgradeStep> upgradeSteps = new TreeSet<>(); List<UpgradeScript> orderedScriptsToRun = getOrderedScriptsToRun(); for (int i = 0; i < orderedScriptsToRun.size(); i++) { int scriptIndex = i; UpgradeScript script = orderedScriptsToRun.get(i); // fetch upgradescript metadata UpgradeScript.UpgradeScriptMetadata upgradeScriptMetadata = getUpgradeScriptMetadata(configMetadata, getScriptName(script)); // check if the script need to be executed MMPVersion currentScriptVersion = getScriptVersion(script); Optional<MMPVersion> lastExecutedVersion = Optional.ofNullable(upgradeScriptMetadata.getLastVersionExecuted()) .map((currVer)->{ // conditionally map deprecated non-conforming versions... String mappedVersion = upgradeSettings.getDeprecatedVersionsMapping().get(currVer); if (mappedVersion!=null) { return mappedVersion; } else return currVer; }) .map(MMPVersion::fromStringVersion); if (lastExecutedVersion.isPresent() && lastExecutedVersion.get().compareTo(currentScriptVersion) == 0) { log.info("Skipping upgrade script '{}' because current version '{}' is not newer than the last executed one ('{}')", script.getClass().getName(), currentScriptVersion, upgradeScriptMetadata.getLastVersionExecuted()); continue; } else if (lastExecutedVersion.isPresent() && lastExecutedVersion.get().compareTo(currentScriptVersion) > 0) { log.warn("Suspicious state - upgrade script '{}' is skipped because current version '{}' is OLDER than the last executed one ('{}')", script.getClass().getName(), currentScriptVersion, upgradeScriptMetadata.getLastVersionExecuted()); continue; } log.info("Including upgrade script '{}' (this version '{}', last executed version '{}')", script.getClass().getName(), currentScriptVersion, lastExecutedVersion.orElse(null)); // collect pieces and prepare context Map<String, Object> scriptConfig = (Map<String, Object>) upgradeSettings.getUpgradeConfig().get(getScriptName(script)); UpgradeScript.UpgradeContext upgradeContext = new UpgradeScript.UpgradeContext( configMetadata.getVersion(), upgradeSettings.getUpgradeToVersion(), props, scriptConfig, dicomConfigurationManager.getConfigurationStorage(), dicomConfigurationManager, upgradeScriptMetadata, configMetadata ); // include the relevant upgrade steps if (script instanceof VersionDrivenUpgradeScript) { VersionDrivenUpgradeScript versionDrivenUpgradeScript = (VersionDrivenUpgradeScript) script; versionDrivenUpgradeScript.init(upgradeContext); if (!lastExecutedVersion.isPresent()) { upgradeSteps.add(new UpgradeStep().edit((s) -> { s.action = () -> invokeMethod(versionDrivenUpgradeScript, versionDrivenUpgradeScript.getFirstRunMethod()); s.version = currentScriptVersion; s.scriptIndex = scriptIndex; s.label = "Running upgrade script "+getScriptName(script)+" for the first time - invoking method " + versionDrivenUpgradeScript.getFirstRunMethod().getName(); })); } else { versionDrivenUpgradeScript.getFixUpMethods(lastExecutedVersion.get()) .entrySet().stream() .map((e) -> new UpgradeStep().edit((s) -> { s.action = () -> invokeMethod(versionDrivenUpgradeScript, e.getValue()); s.version = e.getKey(); s.scriptIndex = scriptIndex; s.label="["+getScriptName(script)+"] Invoking fix-up method "+e.getValue().getName()+" (FixUpTo "+e.getKey()+")"; })) .forEach(upgradeSteps::add); } } else { upgradeSteps.add(new UpgradeStep().edit((s) -> { s.action = () -> script.upgrade(upgradeContext); s.version = currentScriptVersion; s.scriptIndex = scriptIndex; s.label = "Invoking upgrade() method of " + getScriptName(script); })); } } return upgradeSteps; } private String getScriptName(UpgradeScript s) { return s.getClass().getName(); } private ConfigurationMetadata loadOrInitConfigMetadata() { ConfigurationMetadata configMetadata = dicomConfigurationManager .getTypeSafeConfiguration() .load(DicomConfigurationManager.METADATA_ROOT_PATH, ConfigurationMetadata.class); if (configMetadata == null) { configMetadata = new ConfigurationMetadata(); } return configMetadata; } private MMPVersion getScriptVersion(UpgradeScript script) { try { ScriptVersion scriptVersion = script.getClass().getAnnotation(ScriptVersion.class); // mapping of deprecated non-conformant versions... String strValue = Optional.ofNullable(scriptVersion).map(ScriptVersion::value).orElse(UpgradeScript.NO_VERSION); if (!strValue.isEmpty()) { String mappedVersion = upgradeSettings.getDeprecatedVersionsMapping().get(strValue); if (mappedVersion!=null) { return MMPVersion.fromStringVersion(mappedVersion); } } return MMPVersion.fromScriptVersionAnno(scriptVersion); } catch (IllegalArgumentException e ) { throw new RuntimeException("Upgrade script " + script.getClass().getName() + " has an invalid version", e); } } private UpgradeScript.UpgradeScriptMetadata getUpgradeScriptMetadata(ConfigurationMetadata configMetadata, String upgradeScriptName) { UpgradeScript.UpgradeScriptMetadata upgradeScriptMetadata = configMetadata.getMetadataOfUpgradeScripts().get(upgradeScriptName); if (upgradeScriptMetadata == null) { upgradeScriptMetadata = new UpgradeScript.UpgradeScriptMetadata(); configMetadata.getMetadataOfUpgradeScripts().put(upgradeScriptName, upgradeScriptMetadata); } return upgradeScriptMetadata; } private void invokeMethod(Object upgScr, Method m) { try { m.invoke(upgScr); } catch (Exception e) { throw new RuntimeException("Cannot invoke a method of an upgrade script " + upgScr.getClass().getName() + " . " + m.getName(), e); } } }