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.vindoz.InstallAfterRestart;
028import org.nuxeo.connect.client.we.StudioSnapshotHelper;
029import org.nuxeo.connect.connector.ConnectServerError;
030import org.nuxeo.connect.data.DownloadablePackage;
031import org.nuxeo.connect.data.DownloadingPackage;
032import org.nuxeo.connect.packages.PackageManager;
033import org.nuxeo.connect.packages.dependencies.TargetPlatformFilterHelper;
034import org.nuxeo.connect.update.LocalPackage;
035import org.nuxeo.connect.update.PackageException;
036import org.nuxeo.connect.update.PackageState;
037import org.nuxeo.connect.update.PackageUpdateService;
038import org.nuxeo.connect.update.ValidationStatus;
039import org.nuxeo.connect.update.task.Task;
040import org.nuxeo.ecm.admin.runtime.PlatformVersionHelper;
041import org.nuxeo.ecm.automation.core.Constants;
042import org.nuxeo.ecm.automation.core.annotations.Context;
043import org.nuxeo.ecm.automation.core.annotations.Operation;
044import org.nuxeo.ecm.automation.core.annotations.OperationMethod;
045import org.nuxeo.ecm.automation.core.annotations.Param;
046import org.nuxeo.ecm.core.api.CoreSession;
047import org.nuxeo.ecm.core.api.NuxeoException;
048import org.nuxeo.ecm.core.api.NuxeoPrincipal;
049import org.nuxeo.runtime.api.Framework;
050
051/**
052 * Operation to trigger a Hot reload of the Studio Snapshot package. You must be an administrator to trigger it.
053 *
054 * @since 8.2
055 */
056@Operation(id = HotReloadStudioSnapshot.ID, category = Constants.CAT_SERVICES, label = "Hot Reload Studio Snapshot Package", description = "Updates Studio project with latest snapshot.")
057public class HotReloadStudioSnapshot {
058
059    public static final String ID = "Service.HotReloadStudioSnapshot";
060
061    protected static boolean updateInProgress = false;
062
063    private static final Log log = LogFactory.getLog(HotReloadStudioSnapshot.class);
064
065    @Context
066    protected CoreSession session;
067
068    @Context
069    protected PackageManager pm;
070
071    @Param(name = "validate", required = false)
072    protected boolean validate;
073
074    @OperationMethod
075    public void run() throws Exception {
076        if (updateInProgress) {
077            return;
078        }
079
080        if (!((NuxeoPrincipal) session.getPrincipal()).isAdministrator()) {
081            throw new NuxeoException("Must be Administrator to use this function");
082        }
083
084        List<DownloadablePackage> pkgs = pm.listRemoteAssociatedStudioPackages();
085        DownloadablePackage snapshotPkg = StudioSnapshotHelper.getSnapshot(pkgs);
086
087        if (snapshotPkg == null) {
088            throw new NuxeoException("No Snapshot Package was found.");
089        }
090
091        try {
092            updateInProgress = true;
093            hotReloadPackage(snapshotPkg);
094        } finally {
095            updateInProgress = false;
096        }
097    }
098
099    public void hotReloadPackage(DownloadablePackage remotePkg) {
100        if (validate) {
101            pm.flushCache();
102
103            String targetPlatform = PlatformVersionHelper.getPlatformFilter();
104            if (!TargetPlatformFilterHelper.isCompatibleWithTargetPlatform(remotePkg, targetPlatform)) {
105                throw new NuxeoException(String.format("This package is not validated for your current platform: %s",
106                        targetPlatform));
107            }
108        }
109
110        // Effective install
111        if (Framework.isDevModeSet()) {
112            try {
113                PackageUpdateService pus = Framework.getLocalService(PackageUpdateService.class);
114                String packageId = remotePkg.getId();
115                LocalPackage pkg = pus.getPackage(packageId);
116
117                // Uninstall and/or remove if needed
118                if (pkg != null) {
119                    removePackage(pus, pkg);
120                }
121
122                // Download
123                DownloadingPackage downloadingPkg = pm.download(packageId);
124                while (!downloadingPkg.isCompleted()) {
125                    log.debug("Downloading studio snapshot package: " + packageId);
126                    Thread.sleep(100);
127                }
128
129                // Install
130                log.info("Installing " + packageId);
131                pkg = pus.getPackage(packageId);
132                if (pkg == null || PackageState.DOWNLOADED != pkg.getPackageState()) {
133                    throw new NuxeoException("Error while downloading studio snapshot " + pkg);
134                }
135                Task installTask = pkg.getInstallTask();
136                try {
137                    performTask(installTask);
138                } catch (PackageException e) {
139                    installTask.rollback();
140                    throw e;
141                }
142            } catch (InterruptedException e) {
143                ExceptionUtils.checkInterrupt(e);
144                throw new NuxeoException("Error while downloading studio snapshot", e);
145            } catch (PackageException | ConnectServerError e) {
146                throw new NuxeoException("Error while installing studio snapshot", e);
147            }
148        } else {
149            InstallAfterRestart.addPackageForInstallation(remotePkg.getId());
150        }
151    }
152
153    protected static void removePackage(PackageUpdateService pus, LocalPackage pkg) throws PackageException {
154        log.info(String.format("Removing package %s before update...", pkg.getId()));
155        if (pkg.getPackageState().isInstalled()) {
156            // First remove it to allow SNAPSHOT upgrade
157            log.info("Uninstalling " + pkg.getId());
158            Task uninstallTask = pkg.getUninstallTask();
159            try {
160                performTask(uninstallTask);
161            } catch (PackageException e) {
162                uninstallTask.rollback();
163                throw e;
164            }
165        }
166        pus.removePackage(pkg.getId());
167    }
168
169    protected static void performTask(Task task) throws PackageException {
170        ValidationStatus validationStatus = task.validate();
171        if (validationStatus.hasErrors()) {
172            throw new PackageException("Failed to validate package " + task.getPackage().getId() + " -> "
173                    + validationStatus.getErrors());
174        }
175        if (validationStatus.hasWarnings()) {
176            log.warn("Got warnings on package validation " + task.getPackage().getId() + " -> "
177                    + validationStatus.getWarnings());
178        }
179        task.run(null);
180    }
181}