001/*
002 * (C) Copyright 2016 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Nuxeo
018 */
019
020package org.nuxeo.ecm.admin.operation;
021
022import java.util.List;
023
024import org.apache.commons.logging.Log;
025import org.apache.commons.logging.LogFactory;
026import org.nuxeo.common.utils.ExceptionUtils;
027import org.nuxeo.connect.client.we.StudioSnapshotHelper;
028import org.nuxeo.connect.connector.ConnectServerError;
029import org.nuxeo.connect.data.DownloadablePackage;
030import org.nuxeo.connect.data.DownloadingPackage;
031import org.nuxeo.connect.packages.PackageManager;
032import org.nuxeo.connect.packages.dependencies.TargetPlatformFilterHelper;
033import org.nuxeo.connect.update.LocalPackage;
034import org.nuxeo.connect.update.PackageException;
035import org.nuxeo.connect.update.PackageState;
036import org.nuxeo.connect.update.PackageUpdateService;
037import org.nuxeo.connect.update.ValidationStatus;
038import org.nuxeo.connect.update.task.Task;
039import org.nuxeo.ecm.admin.runtime.PlatformVersionHelper;
040import org.nuxeo.ecm.automation.core.Constants;
041import org.nuxeo.ecm.automation.core.annotations.Context;
042import org.nuxeo.ecm.automation.core.annotations.Operation;
043import org.nuxeo.ecm.automation.core.annotations.OperationMethod;
044import org.nuxeo.ecm.automation.core.annotations.Param;
045import org.nuxeo.ecm.core.api.CoreSession;
046import org.nuxeo.ecm.core.api.NuxeoException;
047import org.nuxeo.ecm.core.api.NuxeoPrincipal;
048import org.nuxeo.runtime.api.Framework;
049
050/**
051 * Operation to trigger a Hot reload of the Studio Snapshot package. You must be an administrator to trigger it.
052 *
053 * @since 8.2
054 */
055@Operation(id = HotReloadStudioSnapshot.ID, category = Constants.CAT_SERVICES, label = "Hot Reload Studio Snapshot Package", description = "Updates Studio project with latest snapshot.")
056public class HotReloadStudioSnapshot {
057
058    public static final String ID = "Service.HotReloadStudioSnapshot";
059
060    protected static boolean updateInProgress = false;
061
062    private static final Log log = LogFactory.getLog(HotReloadStudioSnapshot.class);
063
064    @Context
065    protected CoreSession session;
066
067    @Context
068    protected PackageManager pm;
069
070    @Param(name = "validate", required = false)
071    protected boolean validate = true;
072
073    @OperationMethod
074    public void run() throws Exception {
075        if (updateInProgress) {
076            return;
077        }
078
079        if (!((NuxeoPrincipal) session.getPrincipal()).isAdministrator()) {
080            throw new NuxeoException("Must be Administrator to use this function");
081        }
082
083        if (!Framework.isDevModeSet()) {
084            throw new NuxeoException("You must enable Dev mode to Hot reload your Studio Snapshot package.");
085        }
086
087        List<DownloadablePackage> pkgs = pm.listRemoteAssociatedStudioPackages();
088        DownloadablePackage snapshotPkg = StudioSnapshotHelper.getSnapshot(pkgs);
089
090        if (snapshotPkg == null) {
091            throw new NuxeoException("No Snapshot Package was found.");
092        }
093
094        try {
095            updateInProgress = true;
096            hotReloadPackage(snapshotPkg);
097        } finally {
098            updateInProgress = false;
099        }
100    }
101
102    public void hotReloadPackage(DownloadablePackage remotePkg) {
103        if (validate) {
104            pm.flushCache();
105
106            String targetPlatform = PlatformVersionHelper.getPlatformFilter();
107            if (!TargetPlatformFilterHelper.isCompatibleWithTargetPlatform(remotePkg, targetPlatform)) {
108                throw new NuxeoException(
109                        String.format("This package is not validated for your current platform: %s", targetPlatform));
110            }
111        }
112
113        // Effective install
114        try {
115            PackageUpdateService pus = Framework.getLocalService(PackageUpdateService.class);
116            String packageId = remotePkg.getId();
117            LocalPackage pkg = pus.getPackage(packageId);
118
119            // Uninstall and/or remove if needed
120            if (pkg != null) {
121                removePackage(pus, pkg);
122            }
123
124            // Download
125            DownloadingPackage downloadingPkg = pm.download(packageId);
126            while (!downloadingPkg.isCompleted()) {
127                log.debug("Downloading studio snapshot package: " + packageId);
128                Thread.sleep(100);
129            }
130
131            // Install
132            log.info("Installing " + packageId);
133            pkg = pus.getPackage(packageId);
134            if (pkg == null || PackageState.DOWNLOADED != pkg.getPackageState()) {
135                throw new NuxeoException("Error while downloading studio snapshot " + pkg);
136            }
137            Task installTask = pkg.getInstallTask();
138            try {
139                performTask(installTask);
140            } catch (PackageException e) {
141                installTask.rollback();
142                throw e;
143            }
144        } catch (InterruptedException e) {
145            ExceptionUtils.checkInterrupt(e);
146            throw new NuxeoException("Error while downloading studio snapshot", e);
147        } catch (PackageException | ConnectServerError e) {
148            throw new NuxeoException("Error while installing studio snapshot", e);
149        }
150    }
151
152    protected static void removePackage(PackageUpdateService pus, LocalPackage pkg) throws PackageException {
153        log.info(String.format("Removing package %s before update...", pkg.getId()));
154        if (pkg.getPackageState().isInstalled()) {
155            // First remove it to allow SNAPSHOT upgrade
156            log.info("Uninstalling " + pkg.getId());
157            Task uninstallTask = pkg.getUninstallTask();
158            try {
159                performTask(uninstallTask);
160            } catch (PackageException e) {
161                uninstallTask.rollback();
162                throw e;
163            }
164        }
165        pus.removePackage(pkg.getId());
166    }
167
168    protected static void performTask(Task task) throws PackageException {
169        ValidationStatus validationStatus = task.validate();
170        if (validationStatus.hasErrors()) {
171            throw new PackageException(
172                    "Failed to validate package " + task.getPackage().getId() + " -> " + validationStatus.getErrors());
173        }
174        if (validationStatus.hasWarnings()) {
175            log.warn("Got warnings on package validation " + task.getPackage().getId() + " -> "
176                    + validationStatus.getWarnings());
177        }
178        task.run(null);
179    }
180}