All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.adobe.cq.upgradesexecutor.Activator Maven / Gradle / Ivy

/*************************************************************************
 *
 * ADOBE CONFIDENTIAL
 * __________________
 *
 *  Copyright 2012 Adobe Systems Incorporated
 *  All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Adobe Systems Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Adobe Systems Incorporated and its
 * suppliers and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Adobe Systems Incorporated.
 **************************************************************************/
package com.adobe.cq.upgradesexecutor;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.jcr.Item;
import javax.jcr.RepositoryException;
import javax.jcr.Session;

import com.adobe.granite.ui.clientlibs.HtmlLibraryManager;
import com.day.cq.compat.codeupgrade.CodeUpgradeTask;
import com.day.cq.compat.codeupgrade.CodeUpgradeTaskFilter;
import com.day.cq.compat.codeupgrade.MigrationModeCheck;
import org.apache.commons.collections.CollectionUtils;
import org.apache.sling.installer.api.info.Resource;
import org.apache.sling.hc.api.Result;
import org.apache.sling.hc.api.ResultLog;
import org.apache.sling.hc.api.execution.HealthCheckExecutionResult;
import org.apache.sling.hc.api.execution.HealthCheckExecutor;
import org.apache.sling.installer.api.info.ResourceGroup;
import org.apache.sling.jcr.api.SlingRepository;
import org.apache.sling.launchpad.api.StartupHandler;
import org.apache.sling.launchpad.api.StartupMode;
import org.apache.sling.settings.SlingSettingsService;
import org.apache.sling.startupfilter.StartupInfoProvider;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.util.tracker.ServiceTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/** Run CodeUpgradeTasks, synchronously in the Activator.start() method */
public class Activator implements BundleActivator {
    private final Logger log = LoggerFactory.getLogger(getClass());
    /** If this property is true, upgrades execute whatever the StartupMode is.
     *  Useful for troubleshooting the upgrade code. */
    public static final String FORCE_UPGRADES_PATH = "/var/upgrade/status/upgradesExecutor.forceUpgrades";

    private ExecutionConditionCodeUpgradeTaskServiceTracker st;

    final List tasks = new ArrayList();

    private Set cachedActiveResourceURLSet = new HashSet();

    private static final int OSGI_INSTALLER_NO_ACTIVITY_TIMEOUT = 60000;

    private long cachedNoActivityOsgiInstallerTime = 0;

    /** Provides informational status messages in the 503 responses that CQ
     *  returns during startup.
     */
    class InfoProvider implements StartupInfoProvider {

        private String progressInfo = "Initializing";
        private volatile CodeUpgradeTask task;

        public String getProgressInfo() {
            if (task != null) {
                String info = task.getProgressInfo();
                if (info == null) {
                    info = "No info yet";
                }
                return "Running " + task + ": " + info;
            }
            return progressInfo;
        }

        void say(String info) {
            log.info(info);
            progressInfo = info;
        }

        void setTask(CodeUpgradeTask t) {
            task = t;
        }
    }

    /** When this bundle starts, execute all available CodeUpgradeTasks
     *  if StartupMode is UPDATE.
     */
    public void start(BundleContext context) throws Exception {
        // Nothing to do unless startup mode is UPDATE
        final ServiceReference ref = context.getServiceReference(StartupHandler.class.getName());
        if (ref == null) {
            throw new IllegalStateException(
                    "StartupHandler not found, cannot decide whether to run upgrade code or not");
        }
        final StartupHandler sh = (StartupHandler) context.getService(ref);
        final StartupMode sm = sh.getMode();

        final ServiceReference migCheckRef = context.getServiceReference(MigrationModeCheck.class.getName());

        ServiceTracker migCheckTracker = new ServiceTracker(context, migCheckRef, null);
        migCheckTracker.open();

        MigrationModeCheck checker = null;
        try {
            checker = (MigrationModeCheck)migCheckTracker.waitForService(2 * 60 * 1000);
        } catch (Exception ex) {
           //  handling via null check
        } finally {
            migCheckTracker.close();
        }

        if (checker == null) {
            log.error("Could not get MigrationModeCheck service, execution of CodeUpgradeTasks aborted");
            return;
        }
        boolean delayedMigrationActive = checker.isDelayedMigrationActive();

        // If we have a repository service and the FORCE_UPGRADES_PATH property is
        // true, force upgrade code to run
        if (!delayedMigrationActive && sm != StartupMode.UPDATE ) {
            boolean force = false;
            final ServiceReference repoRef = context.getServiceReference(SlingRepository.class.getName());
            if (repoRef != null) {
                final SlingRepository repo = (SlingRepository) context.getService(repoRef);
                final Session s = repo.loginAdministrative(repo.getDefaultWorkspace());
                try {
                    if (s.itemExists(FORCE_UPGRADES_PATH)) {
                        final Item it = s.getItem(FORCE_UPGRADES_PATH);
                        if (!it.isNode()) {
                            force = s.getProperty(FORCE_UPGRADES_PATH).getBoolean();
                            log.warn("{}=true will force upgrade code to run every time this bundle is started - "
                                            + " if this is not desired, remove that property",
                                    FORCE_UPGRADES_PATH);
                        }
                    }
                } finally {
                    s.logout();
                }
            }

            if (force) {
                log.info("StartupMode is {} but {} is true, executing upgrade tasks", sm, FORCE_UPGRADES_PATH);
            } else {
                log.info("UPGRADE NOT NEEDED - StartupMode is {}", sm);
                return;
            }
        }
        
        log.info("UPGRADE / Content Migration STARTS - StartupMode is {}", sm);
        

        // StartupInfoProvider progress info is included in the 503
        // response that CQ provides during startup
        final InfoProvider ip = new InfoProvider();
        ServiceRegistration reg = null;
        try {
            reg = context.registerService(StartupInfoProvider.class.getName(), ip, null);
            final ServiceReference slingInfoProviderServiceRef = context.getServiceReference(
                    org.apache.sling.installer.api.info.InfoProvider.class.getName());
            final org.apache.sling.installer.api.info.InfoProvider slingInfoProvider = slingInfoProviderServiceRef != null ?
                    (org.apache.sling.installer.api.info.InfoProvider) context.getService(slingInfoProviderServiceRef) : null;

            st = new ExecutionConditionCodeUpgradeTaskServiceTracker(context);
            st.open();

            while (true) {
                Thread.sleep(1000);
                if (st.isCodeUpgradeExecutionConditonSatisfied()) {
                    finishUpgradeExecution(context, ip);
                    break;
                } else if (timeout(slingInfoProvider)) {
                    log.error("OSGI installer finished and the upgrade task condition was not satisfied. Executing installed upgrade tasks" +
                            " but most likely one of the mandatory upgrade tasks was not executed. Please check the log file.");
                    finishUpgradeExecution(context, ip);
                    break;
                }
            }
        } finally {
            if (reg != null) {
                reg.unregister();
            }
            st.close();
        }
    }

    private boolean timeout(org.apache.sling.installer.api.info.InfoProvider slingInfoProvider) throws InterruptedException {
        if (slingInfoProvider != null) {
            List currentActiveResources = slingInfoProvider.getInstallationState().getActiveResources();
            Set currentActiveResourceURLSet = extractResourceURLSet(currentActiveResources);

            // if no change in osgi installer for more then {@code OSGI_INSTALLER_NO_ACTIVITY_TIMEOUT} then timeout is activated
            if (CollectionUtils.isEqualCollection(currentActiveResourceURLSet, cachedActiveResourceURLSet)) {
                long currentTime = System.currentTimeMillis();
                if (cachedNoActivityOsgiInstallerTime == 0) {
                    cachedNoActivityOsgiInstallerTime = currentTime;
                } else {
                    if (currentTime - cachedNoActivityOsgiInstallerTime > OSGI_INSTALLER_NO_ACTIVITY_TIMEOUT) {
                        return true;
                    }
                }
            } else {
                cachedNoActivityOsgiInstallerTime = 0;
            }
            cachedActiveResourceURLSet = currentActiveResourceURLSet;
            return false;
        } else {
            log.error("Could not obtain OSGI installer state. Waiting 2 minutes for the upgrade tasks to install and then execute them.");
            Thread.sleep(120000);
            return true;
        }
    }

    private Set extractResourceURLSet(List currentActiveResources) {
        Set resources = new HashSet();
        for (ResourceGroup resourceGroup : currentActiveResources) {
            for (Resource resource : resourceGroup.getResources()) {
                resources.add(resource.getURL());
            }
        }
        return resources;
    }

    private void finishUpgradeExecution(BundleContext context, InfoProvider ip) {
        runUpgradeTasks(context, ip);
        invalidateCaches(context);
        executePostUpgradeHC(context);
        updateInstallPropertiesFile(context);
    }

    private class ExecutionConditionCodeUpgradeTaskServiceTracker extends ServiceTracker {
        private  boolean codeUpgradeExecutionConditonSatisfied;

        public ExecutionConditionCodeUpgradeTaskServiceTracker(BundleContext context) {
            super(context, CodeUpgradeTask.class.getName(), null);
        }

        @Override
        public T addingService(ServiceReference reference) {
            T service = super.addingService(reference);
            if ("com.day.cq.compat.codeupgrade.impl.ExecutionConditionCodeUpgradeTask".equals(service.getClass().getName())) {
                codeUpgradeExecutionConditonSatisfied = true;
            }
            return service;
        }

        public boolean isCodeUpgradeExecutionConditonSatisfied() {
            return codeUpgradeExecutionConditonSatisfied;
        }
    }

    //if a temporary property file exists, then it contains the new build number and we must updated the quickstart.properties file
    private void updateInstallPropertiesFile(BundleContext context) {
        final ServiceReference ref = context.getServiceReference(SlingSettingsService.class.getName());
        if (ref == null) {
            throw new IllegalStateException("SlingSettingsService not found.");
        }
        SlingSettingsService slingSettings = (SlingSettingsService) context.getService(ref);
        File confFolder = new File(slingSettings.getSlingHomePath(), "conf");
        File tmpInstallFile = new File(confFolder, "quickstart.properties_tmp");
        if (tmpInstallFile.exists()) {
            File installFile = new File(confFolder, "quickstart.properties");
            if (installFile.exists() && installFile.delete()) {
                tmpInstallFile.renameTo(installFile);
            }
        }
    }

    private void executePostUpgradeHC(BundleContext context) {
        log.info("Start executing post-upgrade health checks.");
        final ServiceReference ref = context.getServiceReference(HealthCheckExecutor.class.getName());
        if (ref == null) {
            throw new IllegalStateException("HealthCheckExecutor not found.");
        }
        final HealthCheckExecutor healthCheckExecutor = (HealthCheckExecutor) context.getService(ref);
        for (HealthCheckExecutionResult healthCheckExecutionResult : healthCheckExecutor.execute("post-upgrade")) {
            Result result = healthCheckExecutionResult.getHealthCheckResult();
            String hcName = healthCheckExecutionResult.getHealthCheckMetadata().getName();
            if (!result.isOk()) {
                log.error("{} health check failed:", hcName);
                for (Iterator it = result.iterator(); it.hasNext();) {
                    ResultLog.Entry logEntry = it.next();
                    Result.Status status = logEntry.getStatus();
                    if (Result.Status.INFO != status && Result.Status.DEBUG != status) {
                        log.error(logEntry.getMessage());
                    }
                }
            } else {
                log.info("{} health check ran successfully.", hcName);
            }
        }
        log.info("Finished executing post-upgrade health checks.");
    }

    public void stop(BundleContext context) throws Exception {
        if (st != null) {
            st.close();
        }
    }

    private void runUpgradeTasks(BundleContext context, InfoProvider ip) {
        ip.say("Collecting CodeUpgradeTasks");

        try {
            // Get CodeUpgradeTaskFilters
            final ServiceReference[] filterRefs = context.getServiceReferences(CodeUpgradeTaskFilter.class.getName(), null);
            log.info("UPGRADE TASK FILTERS - {} {} services found", filterRefs.length, CodeUpgradeTaskFilter.class.getName());
            if (filterRefs != null && filterRefs.length > 0) {
                Arrays.sort(filterRefs);
            }

            List filters = new LinkedList();
            for (ServiceReference filterRef : filterRefs) {
                filters.add((CodeUpgradeTaskFilter) context.getService(filterRef));
            }

            // Get tasks and sort by service ranking
            final ServiceReference[] refs = context.getServiceReferences(CodeUpgradeTask.class.getName(), null);
            if (refs == null || refs.length < 1) {
                log.info("NO UPGRADE TASKS - no {} services found, nothing to do", CodeUpgradeTask.class.getName());
                return;
            }
            Arrays.sort(refs);

            // Collect tasks
            final List tasks = new ArrayList();
            for (ServiceReference ref : refs) {
                CodeUpgradeTask cut = (CodeUpgradeTask) context.getService(ref);
                boolean isSkipped = false;
                Iterator filterIt = filters.iterator();
                while (filterIt.hasNext() && !isSkipped) {
                    CodeUpgradeTaskFilter filter = filterIt.next();
                    log.debug("Checking CodeUpgradeTask {} with CodeUpgradeTaskFilter {}", cut.getClass().getSimpleName(),
                            filter.getClass().getSimpleName());
                    isSkipped |= filter.isSkipped(cut);
                    if (isSkipped) {
                        log.info("Skipped CodeUpgradeTask {} due to CodeUpgradeTaskFilter {}", cut.getClass().getSimpleName(),
                                filter.getClass().getSimpleName());
                    }
                }
                if (!isSkipped) {
                    tasks.add(cut);
                }
            }

            // Execute tasks
            final long startTime = System.currentTimeMillis();
            ip.say("Checking " + refs.length + " candidate CodeUpgradeTasks: " + tasks);
            int successful = 0;
            int failed = 0;
            int skipped = 0;
            for (CodeUpgradeTask t : tasks) {
                try {
                    if (t.upgradeNeeded()) {
                        ip.say("UPGRADE TASK STARTING: " + t);
                        ip.setTask(t);
                        t.run();
                        ip.setTask(null);
                        ip.say("UPGRADE TASK DONE: " + t);
                        successful++;
                    } else {
                        skipped++;
                        ip.say("UPGRADE TASK SKIPPED: " + t);
                    }
                } catch (RuntimeException e) {
                    failed++;
                    ip.say("UPGRADE TASK FAILED: " + t);
                } finally {
                    ip.setTask(null);
                }
            }

            final long elapsed = System.currentTimeMillis() - startTime;
            log.info("UPGRADE FINISHED: From a total of {} CodeUpgradeTasks: {} were successfully executed, {} failed and {} skipped. " +
                    "Total execution time about {} seconds", refs.length, successful, failed, skipped, elapsed / 1000);
        } catch (InvalidSyntaxException e) {
            log.error("Could not get ServiceReferences", e);
        }
    }

    private void invalidateCaches(BundleContext context) {
        final ServiceReference ref = context.getServiceReference(HtmlLibraryManager.class);
        if (ref != null) {
            HtmlLibraryManager mgr = context.getService(ref);
            try {
                mgr.invalidateOutputCache();
                log.info("Cleaned HtmlLibraryManager output cache.");
            } catch (RepositoryException e) {
                log.error("Can't clean HtmlLibraryManager output cache.");
            }
        } else {
            log.error("Reference to HtmlLibraryManager not found - couldn't clean output cache.");
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy