/* * Copyright (C) 2005 Alfresco, Inc. * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a * copy of the License at * * http://www.alfresco.org/legal/license.txt * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific * language governing permissions and limitations under the * License. */ package org.alfresco.repo.admin.patch; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.domain.AppliedPatch; import org.alfresco.service.cmr.admin.PatchException; import org.alfresco.service.cmr.admin.PatchInfo; import org.alfresco.service.descriptor.Descriptor; import org.alfresco.service.descriptor.DescriptorService; 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 Log logger = LogFactory.getLog(PatchServiceImpl.class); private DescriptorService descriptorService; private PatchDaoService patchDaoService; private List patches; public void setDescriptorService(DescriptorService descriptorService) { this.descriptorService = descriptorService; } public void setPatchDaoService(PatchDaoService patchDaoService) { this.patchDaoService = patchDaoService; } public void setPatches(List patches) { this.patches = patches; } public List getAppliedPatches() { // get all the persisted patches List appliedPatches = patchDaoService.getAppliedPatches(); List patchInfos = new ArrayList(appliedPatches.size()); for (AppliedPatch patch : appliedPatches) { PatchInfo patchInfo = new PatchInfo(patch); patchInfos.add(patchInfo); } // done if (logger.isDebugEnabled()) { logger.debug("Retrieved list of " + patchInfos.size() + " applied patches: \n"); } return patchInfos; } public boolean applyOutstandingPatches() { // construct a map of all known patches by ID Map allPatchesById = new HashMap(23); for (Patch patch : patches) { allPatchesById.put(patch.getId(), patch); } // construct a list of executed patches by ID Map appliedPatchInfosById = new HashMap(23); List appliedPatches = getAppliedPatches(); for (PatchInfo patchInfo : appliedPatches) { // ignore unsuccessful attempts - we need to try them again if (!patchInfo.getSucceeded()) { continue; } appliedPatchInfosById.put(patchInfo.getId(), patchInfo); } // go through all the patches and apply them where necessary boolean success = true; for (Patch patch : allPatchesById.values()) { // apply the patch success = applyPatchAndDependencies(patch, appliedPatchInfosById); if (!success) { // we failed to apply a patch or one of its dependencies - terminate break; } } // 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 appliedPatchInfosById already applied patches * @return Returns true if the patch and all its dependencies were successfully applied. */ private boolean applyPatchAndDependencies(Patch patch, Map appliedPatchInfosById) { String id = patch.getId(); // check if it has already been done PatchInfo patchInfo = appliedPatchInfosById.get(id); if (patchInfo != null && patchInfo.getSucceeded()) { // this has already been done return true; } // ensure that dependencies have been done List dependencies = patch.getDependsOn(); for (Patch dependencyPatch : dependencies) { boolean success = applyPatchAndDependencies(dependencyPatch, appliedPatchInfosById); if (!success) { // a patch failed to be applied return false; } } // all the dependencies were successful patchInfo = applyPatch(patch); if (!patchInfo.getSucceeded()) { // this was a failure return false; } else { // it was successful - add it to the map of successful patches appliedPatchInfosById.put(id, patchInfo); return true; } } private PatchInfo applyPatch(Patch patch) { // get the patch from the DAO AppliedPatch appliedPatch = patchDaoService.getAppliedPatch(patch.getId()); if (appliedPatch != null && appliedPatch.getSucceeded()) { // it has already been applied PatchInfo patchInfo = new PatchInfo(appliedPatch); // done if (logger.isDebugEnabled()) { logger.debug("Patch was already successfully applied: \n" + " patch: " + patchInfo); } return patchInfo; } // the execution report String report = null; boolean success = false; // first check whether the patch is relevant to the repo Descriptor repoDescriptor = descriptorService.getRepositoryDescriptor(); boolean applies = applies(repoDescriptor, patch); if (!applies) { // create a dummy report StringBuilder sb = new StringBuilder(128); sb.append("Patch ").append(patch.getId()).append(" was not relevant."); report = sb.toString(); success = true; // this succeeded because it didn't need to be applied } else { // perform actual execution try { report = patch.apply(); success = true; } catch (PatchException e) { // failed report = e.getMessage(); success = false; } } // create a record for the execution appliedPatch = patchDaoService.newAppliedPatch(patch.getId()); // fill in the record's details appliedPatch.setDescription(patch.getDescription()); appliedPatch.setApplyToVersion(patch.getApplyToVersion()); appliedPatch.setSucceeded(success); appliedPatch.setAppliedOnVersion(repoDescriptor.getVersion()); appliedPatch.setAppliedOnDate(new Date()); appliedPatch.setReport(report); // create the info for returning PatchInfo patchInfo = new PatchInfo(appliedPatch); // done if (logger.isDebugEnabled()) { logger.debug("Applied patch: \n" + patchInfo); } return patchInfo; } /** * Check whether or not the patch should be applied to the repository, given the descriptor. * This helper is required to * * @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) { // resolve the version numbers of the repo int[] repoVersions = new int[] {0, 0, 0}; try { if (repoDescriptor.getVersionMajor() != null) { repoVersions[0] = Integer.parseInt(repoDescriptor.getVersionMajor()); } if (repoDescriptor.getVersionMinor() != null) { repoVersions[1] = Integer.parseInt(repoDescriptor.getVersionMinor()); } if (repoDescriptor.getVersionRevision() != null) { repoVersions[2] = Integer.parseInt(repoDescriptor.getVersionRevision()); } } catch (NumberFormatException e) { throw new AlfrescoRuntimeException("Non-numeric repository version are not allowed: " + repoDescriptor.getVersion()); } // by default, the patch won't apply. All the revision numbers will have to be greater // than that of the repo to apply int[] applyToVersions = new int[] {0, 0, 0}; try { // break the patch version up String applyToVersion = patch.getApplyToVersion(); StringTokenizer tokens = new StringTokenizer(applyToVersion, "."); for (int i = 0; i < 3 && tokens.hasMoreTokens(); i++) { String versionStr = tokens.nextToken(); applyToVersions[i] = Integer.parseInt(versionStr); } } catch (NumberFormatException e) { throw new AlfrescoRuntimeException("Non-numeric patch apply version: " + patch); } // compare each version number // in the event of a match, we don't apply as the patch version is the boolean apply = true; for (int i = 0; i < 3; i++) { if (repoVersions[i] < applyToVersions[i]) { // repo is old and patch is newer - we definitely need to apply apply = true; break; } else if (repoVersions[i] > applyToVersions[i]) { // the repo version number is high enough (repo is new enough) that patch doesn't apply apply = false; break; } else { // so far, the two match } } // done if (logger.isDebugEnabled()) { logger.debug("Patch version number check against repo version: \n" + " repo version: " + repoDescriptor.getVersion() + "\n" + " patch: " + patch); } return apply; } }