/* * Copyright (C) 2005-2007 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * As a special exception to the terms and conditions of version 2.0 of * the GPL, you may redistribute this Program in connection with Free/Libre * and Open Source Software ("FLOSS") applications as described in Alfresco's * FLOSS exception. You should have recieved a copy of the text describing * the FLOSS exception, and it is also available here: * http://www.alfresco.com/legal/licensing" */ package org.alfresco.repo.admin.patch; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import org.alfresco.i18n.I18NUtil; import org.alfresco.repo.domain.AppliedPatch; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.admin.PatchException; import org.alfresco.service.cmr.rule.RuleService; import org.alfresco.service.descriptor.Descriptor; import org.alfresco.service.descriptor.DescriptorService; import org.alfresco.service.transaction.TransactionService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Manages patches applied against the repository. *

* Patches are injected into this class and any attempted applications are recorded * for later auditing. * * @since 1.2 * @author Derek Hulley */ public class PatchServiceImpl implements PatchService { private static final String MSG_NOT_RELEVANT = "patch.service.not_relevant"; private static final String MSG_APPLYING_PATCH = "patch.service.applying_patch"; private static final Date ZERO_DATE = new Date(0L); private static final Date INFINITE_DATE = new Date(Long.MAX_VALUE); private static Log logger = LogFactory.getLog(PatchExecuter.class); private DescriptorService descriptorService; private TransactionService transactionService; private RuleService ruleService; private PatchDaoService patchDaoService; private List patches; public PatchServiceImpl() { this.patches = new ArrayList(10); } public void setDescriptorService(DescriptorService descriptorService) { this.descriptorService = descriptorService; } public void setTransactionService(TransactionService transactionService) { this.transactionService = transactionService; } public void setPatchDaoService(PatchDaoService patchDaoService) { this.patchDaoService = patchDaoService; } public void setRuleService(RuleService ruleService) { this.ruleService = ruleService; } public void registerPatch(Patch patch) { patches.add(patch); } public boolean applyOutstandingPatches() { boolean success = true; try { // Disable rules whilst processing the patches this.ruleService.disableRules(); try { // Sort the patches List sortedPatches = new ArrayList(patches); Comparator comparator = new PatchTargetSchemaComparator(); Collections.sort(sortedPatches, comparator); // construct a list of executed patches by ID (also check the date) Map appliedPatchesById = new HashMap(23); List appliedPatches = patchDaoService.getAppliedPatches(); for (final AppliedPatch appliedPatch : appliedPatches) { appliedPatchesById.put(appliedPatch.getId(), appliedPatch); // Update the time of execution if it is null. This is to deal with // patches that get executed prior to server startup and need to have // an execution time assigned if (appliedPatch.getAppliedOnDate() == null) { RetryingTransactionCallback callback = new RetryingTransactionCallback() { public Date execute() throws Throwable { Date now = new Date(); patchDaoService.setAppliedOnDate(appliedPatch.getId(), now); return now; } }; transactionService.getRetryingTransactionHelper().doInTransaction(callback, false, true); } } // go through all the patches and apply them where necessary for (Patch patch : sortedPatches) { // apply the patch success = applyPatchAndDependencies(patch, appliedPatchesById); if (!success) { // we failed to apply a patch or one of its dependencies - terminate break; } } } finally { this.ruleService.enableRules(); } } catch (Throwable exception) { exception.printStackTrace(); } // done return success; } /** * Reentrant method that ensures that a patch and all its dependencies get applied. * The process terminates on the first failure. * * @param patchInfos all the executed patch data. If there was a failure, then this * is the list of successful executions only. * @param patch the patch (containing dependencies) to apply * @param appliedPatchesById already applied patches keyed by their ID * @return Returns true if the patch and all its dependencies were successfully applied. */ private boolean applyPatchAndDependencies(final Patch patch, Map appliedPatchesById) { String id = patch.getId(); // check if it has already been done AppliedPatch appliedPatch = appliedPatchesById.get(id); if (appliedPatch != null && appliedPatch.getSucceeded()) { if (appliedPatch.getWasExecuted() && appliedPatch.getSucceeded()) { // It was sucessfully executed return true; } // We give the patch another chance } // ensure that dependencies have been done List dependencies = patch.getDependsOn(); for (Patch dependencyPatch : dependencies) { boolean success = applyPatchAndDependencies(dependencyPatch, appliedPatchesById); if (!success) { // a patch failed to be applied return false; } } // all the dependencies were successful RetryingTransactionCallback callback = new RetryingTransactionCallback() { public AppliedPatch execute() throws Throwable { return applyPatch(patch); } }; appliedPatch = transactionService.getRetryingTransactionHelper().doInTransaction(callback, false, true); if (!appliedPatch.getSucceeded()) { // this was a failure return false; } else { // it was successful - add it to the map of successful patches appliedPatchesById.put(id, appliedPatch); return true; } } private AppliedPatch applyPatch(Patch patch) { // get the patch from the DAO AppliedPatch appliedPatch = patchDaoService.getAppliedPatch(patch.getId()); // We bypass the patch if it was executed successfully if (appliedPatch != null) { if (appliedPatch.getSucceeded()) { // It has already been successfully applied if (logger.isDebugEnabled()) { logger.debug("Patch was already successfully applied: \n" + " patch: " + appliedPatch); } return appliedPatch; } } // the execution report String report = null; boolean success = false; // first check whether the patch is relevant to the repo Descriptor repoDescriptor = descriptorService.getInstalledRepositoryDescriptor(); boolean applies = applies(repoDescriptor, patch); if (!applies) { // create a dummy report report = I18NUtil.getMessage(MSG_NOT_RELEVANT, repoDescriptor.getSchema()); success = true; // this succeeded because it didn't need to be applied } else { // perform actual execution try { String msg = I18NUtil.getMessage( MSG_APPLYING_PATCH, patch.getId(), I18NUtil.getMessage(patch.getDescription())); logger.info(msg); report = patch.apply(); success = true; } catch (PatchException e) { // failed report = e.getMessage(); success = false; // dump the report to log logger.error(report); } } Descriptor serverDescriptor = descriptorService.getServerDescriptor(); String server = (serverDescriptor.getVersion() + " - " + serverDescriptor.getEdition()); // create or update the record of execution if (appliedPatch == null) { appliedPatch = patchDaoService.newAppliedPatch(patch.getId()); } // fill in the record's details String patchDescription = I18NUtil.getMessage(patch.getDescription()); if (patchDescription == null) { logger.warn("Patch description is not available: " + patch); patchDescription = "No patch description available"; } appliedPatch.setDescription(patchDescription); appliedPatch.setFixesFromSchema(patch.getFixesFromSchema()); appliedPatch.setFixesToSchema(patch.getFixesToSchema()); appliedPatch.setTargetSchema(patch.getTargetSchema()); // the schema the server is expecting appliedPatch.setAppliedToSchema(repoDescriptor.getSchema()); // the old schema of the repo appliedPatch.setAppliedToServer(server); // the current version and label of the server appliedPatch.setAppliedOnDate(new Date()); // the date applied appliedPatch.setSucceeded(success); // whether or not the patch succeeded appliedPatch.setWasExecuted(applies); // whether or not the patch was executed appliedPatch.setReport(report); // additional, human-readable, status // done if (logger.isDebugEnabled()) { logger.debug("Applied patch: \n" + appliedPatch); } return appliedPatch; } /** * Check whether the patch is applicable to the particular version of the repository. * * @param repoDescriptor contains the version details of the repository * @param patch the patch whos version must be checked * @return Returns true if the patch should be applied to the repository */ private boolean applies(Descriptor repoDescriptor, Patch patch) { int repoSchema = repoDescriptor.getSchema(); // does the patch apply? boolean apply = patch.applies(repoSchema); // done if (logger.isDebugEnabled()) { logger.debug("Patch schema version number check against repo version: \n" + " repo schema version: " + repoDescriptor.getVersion() + "\n" + " patch: " + patch); } return apply; } @SuppressWarnings("unchecked") public List getPatches(Date fromDate, Date toDate) { if (fromDate == null) { fromDate = ZERO_DATE; } if (toDate == null) { toDate = INFINITE_DATE; } List appliedPatches = patchDaoService.getAppliedPatches(fromDate, toDate); // disconnect each of these for (PatchInfo appliedPatch : appliedPatches) { patchDaoService.detach((AppliedPatch)appliedPatch); } // done return (List) appliedPatches; } /** * Compares patch target schemas. * * @see Patch#getTargetSchema() * @author Derek Hulley */ private static class PatchTargetSchemaComparator implements Comparator { public int compare(Patch p1, Patch p2) { Integer i1 = new Integer(p1.getTargetSchema()); Integer i2 = new Integer(p2.getTargetSchema()); return i1.compareTo(i2); } } }