org.nuxeo.connect.client.jsf.AppCenterViewsManager.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.connect.client.jsf.AppCenterViewsManager.java

Source

/*
 * (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 *
 * Contributors:
 *     Nuxeo - initial API and implementation
 *
 */

package org.nuxeo.connect.client.jsf;

import java.io.IOException;
import java.io.Serializable;
import java.nio.file.attribute.FileTime;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;

import javax.faces.context.FacesContext;
import javax.faces.model.SelectItem;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.faces.FacesMessages;
import org.jboss.seam.international.StatusMessage;
import org.nuxeo.common.utils.ExceptionUtils;
import org.nuxeo.connect.client.ui.SharedPackageListingsSettings;
import org.nuxeo.connect.client.vindoz.InstallAfterRestart;
import org.nuxeo.connect.client.we.StudioSnapshotHelper;
import org.nuxeo.connect.connector.ConnectServerError;
import org.nuxeo.connect.connector.http.ConnectUrlConfig;
import org.nuxeo.connect.data.DownloadablePackage;
import org.nuxeo.connect.data.DownloadingPackage;
import org.nuxeo.connect.packages.PackageManager;
import org.nuxeo.connect.packages.dependencies.DependencyResolution;
import org.nuxeo.connect.packages.dependencies.TargetPlatformFilterHelper;
import org.nuxeo.connect.update.LocalPackage;
import org.nuxeo.connect.update.PackageDependency;
import org.nuxeo.connect.update.PackageException;
import org.nuxeo.connect.update.PackageState;
import org.nuxeo.connect.update.PackageType;
import org.nuxeo.connect.update.PackageUpdateService;
import org.nuxeo.connect.update.ValidationStatus;
import org.nuxeo.connect.update.task.Task;
import org.nuxeo.ecm.admin.AdminViewManager;
import org.nuxeo.ecm.admin.runtime.PlatformVersionHelper;
import org.nuxeo.ecm.admin.setup.SetupWizardActionBean;
import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils;
import org.nuxeo.ecm.webapp.seam.NuxeoSeamHotReloadContextKeeper;
import org.nuxeo.launcher.config.ConfigurationException;
import org.nuxeo.launcher.config.ConfigurationGenerator;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.services.config.ConfigurationService;

/**
 * Manages JSF views for Package Management.
 *
 * @author <a href="mailto:td@nuxeo.com">Thierry Delprat</a>
 */
@Name("appsViews")
@Scope(ScopeType.CONVERSATION)
public class AppCenterViewsManager implements Serializable {

    private static final long serialVersionUID = 1L;

    protected static final Log log = LogFactory.getLog(AppCenterViewsManager.class);

    private static final String LABEL_STUDIO_UPDATE_STATUS = "label.studio.update.status.";

    /**
     * FIXME JC: should follow or simply reuse {@link PackageState}
     */
    protected enum SnapshotStatus {
        downloading, saving, installing, error, completed, restartNeeded;
    }

    protected static final Map<String, String> view2PackageListName = new HashMap<String, String>() {
        private static final long serialVersionUID = 1L;
        {
            put("ConnectAppsUpdates", "updates");
            put("ConnectAppsStudio", "studio");
            put("ConnectAppsRemote", "remote");
            put("ConnectAppsLocal", "local");
        }
    };

    @In(create = true)
    protected String currentAdminSubViewId;

    @In(create = true)
    protected NuxeoSeamHotReloadContextKeeper seamReloadContext;

    @In(create = true)
    protected SetupWizardActionBean setupWizardAction;

    @In(create = true, required = false)
    protected FacesMessages facesMessages;

    @In(create = true)
    protected Map<String, String> messages;

    protected String searchString;

    protected SnapshotStatus studioSnapshotStatus;

    protected int studioSnapshotDownloadProgress;

    protected boolean isStudioSnapshopUpdateInProgress = false;

    protected String studioSnapshotUpdateError;

    /**
     * Boolean indicating is Studio snapshot package validation should be done.
     *
     * @since 5.7.1
     */
    protected Boolean validateStudioSnapshot;

    /**
     * Last validation status of the Studio snapshot package
     *
     * @since 5.7.1
     */
    protected ValidationStatus studioSnapshotValidationStatus;

    private FileTime lastUpdate = null;

    protected DownloadablePackage studioSnapshotPackage;

    /**
     * Using a dedicated property because studioSnapshotPackage might be null.
     *
     * @since 7.10
     */
    protected Boolean studioSnapshotPackageCached = false;

    public String getSearchString() {
        if (searchString == null) {
            return "";
        }
        return searchString;
    }

    public void setSearchString(String searchString) {
        this.searchString = searchString;
    }

    public boolean getOnlyRemote() {
        return SharedPackageListingsSettings.instance().get("remote").isOnlyRemote();
    }

    public void setOnlyRemote(boolean onlyRemote) {
        SharedPackageListingsSettings.instance().get("remote").setOnlyRemote(onlyRemote);
    }

    protected String getListName() {
        return view2PackageListName.get(currentAdminSubViewId);
    }

    public void setPlatformFilter(boolean doFilter) {
        SharedPackageListingsSettings.instance().get(getListName()).setPlatformFilter(doFilter);
    }

    public boolean getPlatformFilter() {
        return SharedPackageListingsSettings.instance().get(getListName()).getPlatformFilter();
    }

    public String getPackageTypeFilter() {
        return SharedPackageListingsSettings.instance().get(getListName()).getPackageTypeFilter();
    }

    public void setPackageTypeFilter(String filter) {
        SharedPackageListingsSettings.instance().get(getListName()).setPackageTypeFilter(filter);
    }

    public List<SelectItem> getPackageTypes() {
        List<SelectItem> types = new ArrayList<>();
        SelectItem allItem = new SelectItem("", "label.packagetype.all");
        types.add(allItem);
        for (PackageType ptype : PackageType.values()) {
            // if (!ptype.equals(PackageType.STUDIO)) {
            SelectItem item = new SelectItem(ptype.getValue(), "label.packagetype." + ptype.getValue());
            types.add(item);
            // }
        }
        return types;
    }

    public void flushCache() {
        PackageManager pm = Framework.getLocalService(PackageManager.class);
        pm.flushCache();
    }

    /**
     * Method binding for the update button: needs to perform a real redirection (as ajax context is broken after hot
     * reload) and to provide an outcome so that redirection through the URL service goes ok (even if it just reset its
     * navigation handler cache).
     *
     * @since 5.6
     */
    public String installStudioSnapshotAndRedirect() {
        installStudioSnapshot();
        return AdminViewManager.VIEW_ADMIN;
    }

    public void installStudioSnapshot() {
        if (isStudioSnapshopUpdateInProgress) {
            return;
        }
        PackageManager pm = Framework.getLocalService(PackageManager.class);
        // TODO NXP-16228: should directly request the SNAPSHOT package (if only we knew its name!)
        List<DownloadablePackage> pkgs = pm.listRemoteAssociatedStudioPackages();
        DownloadablePackage snapshotPkg = StudioSnapshotHelper.getSnapshot(pkgs);
        studioSnapshotUpdateError = null;
        resetStudioSnapshotValidationStatus();
        if (snapshotPkg != null) {
            isStudioSnapshopUpdateInProgress = true;
            try {
                StudioAutoInstaller studioAutoInstaller = new StudioAutoInstaller(pm, snapshotPkg.getId(),
                        shouldValidateStudioSnapshot());
                studioAutoInstaller.run();
            } finally {
                isStudioSnapshopUpdateInProgress = false;
            }
        } else {
            studioSnapshotUpdateError = translate("label.studio.update.error.noSnapshotPackageFound");
        }
    }

    public boolean isStudioSnapshopUpdateInProgress() {
        return isStudioSnapshopUpdateInProgress;
    }

    /**
     * Returns true if validation should be performed
     *
     * @since 5.7.1
     */
    public Boolean getValidateStudioSnapshot() {
        return validateStudioSnapshot;
    }

    /**
     * @since 5.7.1
     */
    public void setValidateStudioSnapshot(Boolean validateStudioSnapshot) {
        this.validateStudioSnapshot = validateStudioSnapshot;
    }

    /**
     * Returns true if Studio snapshot module should be validated.
     * <p>
     * Validation can be skipped by user, or can be globally disabled by setting framework property
     * "studio.snapshot.disablePkgValidation" to true.
     *
     * @since 5.7.1
     */
    protected boolean shouldValidateStudioSnapshot() {
        ConfigurationService cs = Framework.getService(ConfigurationService.class);
        if (cs.isBooleanPropertyTrue("studio.snapshot.disablePkgValidation")) {
            return false;
        }
        return Boolean.TRUE.equals(getValidateStudioSnapshot());
    }

    protected static String translate(String label, Object... params) {
        return ComponentUtils.translate(FacesContext.getCurrentInstance(), label, params);
    }

    protected FileTime getLastUpdateDate() {
        if (lastUpdate == null) {
            DownloadablePackage snapshotPkg = getStudioProjectSnapshot();
            if (snapshotPkg != null) {
                PackageUpdateService pus = Framework.getLocalService(PackageUpdateService.class);
                try {
                    LocalPackage pkg = pus.getPackage(snapshotPkg.getId());
                    if (pkg != null) {
                        lastUpdate = pus.getInstallDate(pkg.getId());
                    }
                } catch (PackageException e) {
                    log.error(e);
                }
            }
        }
        return lastUpdate;
    }

    /**
     * @since 7.10
     */
    public String getStudioUrl() {
        return ConnectUrlConfig.getStudioUrl(getSnapshotStudioProjectName());
    }

    /**
     * @since 7.10
     */
    public DownloadablePackage getStudioProjectSnapshot() {
        if (!studioSnapshotPackageCached) {
            PackageManager pm = Framework.getLocalService(PackageManager.class);
            // TODO NXP-16228: should directly request the SNAPSHOT package (if only we knew its name!)
            List<DownloadablePackage> pkgs = pm.listRemoteAssociatedStudioPackages();
            studioSnapshotPackage = StudioSnapshotHelper.getSnapshot(pkgs);
            studioSnapshotPackageCached = true;
        }
        return studioSnapshotPackage;
    }

    /**
     * @return null if there is no SNAPSHOT package
     * @since 7.10
     */
    public String getSnapshotStudioProjectName() {
        DownloadablePackage snapshotPkg = getStudioProjectSnapshot();
        if (snapshotPkg != null) {
            return snapshotPkg.getName();
        }
        return null;
    }

    public String getStudioInstallationStatus() {
        if (studioSnapshotStatus == null) {
            LocalPackage pkg = null;
            DownloadablePackage snapshotPkg = getStudioProjectSnapshot();
            if (snapshotPkg != null) {
                try {
                    PackageUpdateService pus = Framework.getLocalService(PackageUpdateService.class);
                    pkg = pus.getPackage(snapshotPkg.getId());
                } catch (PackageException e) {
                    log.error(e);
                }
            }
            if (pkg == null) {
                return translate(LABEL_STUDIO_UPDATE_STATUS + "noStatus");
            }
            PackageState studioPkgState = pkg.getPackageState();
            if (studioPkgState == PackageState.DOWNLOADING) {
                studioSnapshotStatus = SnapshotStatus.downloading;
            } else if (studioPkgState == PackageState.DOWNLOADED) {
                studioSnapshotStatus = SnapshotStatus.saving;
            } else if (studioPkgState == PackageState.INSTALLING) {
                studioSnapshotStatus = SnapshotStatus.installing;
            } else if (studioPkgState.isInstalled()) {
                studioSnapshotStatus = SnapshotStatus.completed;
            } else {
                studioSnapshotStatus = SnapshotStatus.error;
            }
        }

        Object[] params = new Object[0];
        if (SnapshotStatus.error.equals(studioSnapshotStatus)) {
            if (studioSnapshotUpdateError == null) {
                studioSnapshotUpdateError = "???";
            }
            params = new Object[] { studioSnapshotUpdateError };
        } else if (SnapshotStatus.downloading.equals(studioSnapshotStatus)) {
            params = new Object[] { String.valueOf(studioSnapshotDownloadProgress) };
        } else {
            FileTime update = getLastUpdateDate();
            if (update != null) {
                DateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
                df.setTimeZone(TimeZone.getDefault());
                params = new Object[] { df.format(new Date(update.toMillis())) };
            }
        }

        return translate(LABEL_STUDIO_UPDATE_STATUS + studioSnapshotStatus.name(), params);
    }

    // TODO: plug a notifier for status to be shown to the user
    protected class StudioAutoInstaller implements Runnable {

        protected final String packageId;

        protected final PackageManager pm;

        /**
         * @since 5.7.1
         */
        protected final boolean validate;

        protected StudioAutoInstaller(PackageManager pm, String packageId, boolean validate) {
            this.pm = pm;
            this.packageId = packageId;
            this.validate = validate;
        }

        @Override
        public void run() {
            if (validate) {
                ValidationStatus status = new ValidationStatus();

                pm.flushCache();
                DownloadablePackage remotePkg = pm.findRemotePackageById(packageId);
                if (remotePkg == null) {
                    status.addError(
                            String.format("Cannot perform validation: remote package '%s' not found", packageId));
                    return;
                }
                PackageDependency[] pkgDeps = remotePkg.getDependencies();
                if (log.isDebugEnabled()) {
                    log.debug(String.format("%s target platforms: %s", remotePkg,
                            ArrayUtils.toString(remotePkg.getTargetPlatforms())));
                    log.debug(String.format("%s dependencies: %s", remotePkg, ArrayUtils.toString(pkgDeps)));
                }

                // TODO NXP-11776: replace errors by internationalized labels
                String targetPlatform = PlatformVersionHelper.getPlatformFilter();
                if (!TargetPlatformFilterHelper.isCompatibleWithTargetPlatform(remotePkg, targetPlatform)) {
                    status.addError(String.format("This package is not validated for your current platform: %s",
                            targetPlatform));
                }
                // check deps requirements
                if (pkgDeps != null && pkgDeps.length > 0) {
                    DependencyResolution resolution = pm.resolveDependencies(packageId, targetPlatform);
                    if (resolution.isFailed() && targetPlatform != null) {
                        // retry without PF filter in case it gives more information
                        resolution = pm.resolveDependencies(packageId, null);
                    }
                    if (resolution.isFailed()) {
                        status.addError(String.format("Dependency check has failed for package '%s' (%s)",
                                packageId, resolution));
                    } else {
                        List<String> pkgToInstall = resolution.getInstallPackageIds();
                        if (pkgToInstall != null && pkgToInstall.size() == 1
                                && packageId.equals(pkgToInstall.get(0))) {
                            // ignore
                        } else if (resolution.requireChanges()) {
                            // do not install needed deps: they may not be hot-reloadable and that's not what the
                            // "update snapshot" button is for.
                            status.addError(resolution.toString().trim().replaceAll("\n", "<br />"));
                        }
                    }
                }

                if (status.hasErrors()) {
                    setStatus(SnapshotStatus.error, translate("label.studio.update.validation.error"), status);
                    return;
                }
            }

            // Effective install
            if (Framework.isDevModeSet()) {
                try {
                    PackageUpdateService pus = Framework.getLocalService(PackageUpdateService.class);
                    LocalPackage pkg = pus.getPackage(packageId);

                    // Uninstall and/or remove if needed
                    if (pkg != null) {
                        log.info(String.format("Removing package %s before update...", pkg));
                        if (pkg.getPackageState().isInstalled()) {
                            // First remove it to allow SNAPSHOT upgrade
                            log.info("Uninstalling " + packageId);
                            Task uninstallTask = pkg.getUninstallTask();
                            try {
                                performTask(uninstallTask);
                            } catch (PackageException e) {
                                uninstallTask.rollback();
                                throw e;
                            }
                        }
                        pus.removePackage(packageId);
                    }

                    // Download
                    setStatus(SnapshotStatus.downloading, null);
                    DownloadingPackage downloadingPkg = pm.download(packageId);
                    while (!downloadingPkg.isCompleted()) {
                        studioSnapshotDownloadProgress = downloadingPkg.getDownloadProgress();
                        log.debug("downloading studio snapshot package");
                        Thread.sleep(100);
                    }
                    studioSnapshotDownloadProgress = downloadingPkg.getDownloadProgress();
                    setStatus(SnapshotStatus.saving, null);

                    // Install
                    setStatus(SnapshotStatus.installing, null);
                    log.info("Installing " + packageId);
                    pkg = pus.getPackage(packageId);
                    if (pkg == null || PackageState.DOWNLOADED != pkg.getPackageState()) {
                        log.error("Error while downloading studio snapshot " + pkg);
                        setStatus(SnapshotStatus.error, translate("label.studio.update.downloading.error", pkg));
                        return;
                    }
                    Task installTask = pkg.getInstallTask();
                    try {
                        performTask(installTask);
                    } catch (PackageException e) {
                        installTask.rollback();
                        throw e;
                    }
                    // Refresh state
                    pkg = pus.getPackage(packageId);
                    lastUpdate = pus.getInstallDate(packageId);
                    setStatus(SnapshotStatus.completed, null);
                } catch (ConnectServerError e) {
                    setStatus(SnapshotStatus.error, e.getMessage());
                } catch (InterruptedException e) {
                    log.error("Error while downloading studio snapshot", e);
                    setStatus(SnapshotStatus.error,
                            translate("label.studio.update.downloading.error", e.getMessage()));
                    ExceptionUtils.checkInterrupt(e);
                } catch (PackageException e) {
                    log.error("Error while installing studio snapshot", e);
                    setStatus(SnapshotStatus.error,
                            translate("label.studio.update.installation.error", e.getMessage()));
                }
            } else {
                InstallAfterRestart.addPackageForInstallation(packageId);
                setStatus(SnapshotStatus.restartNeeded, null);
                setupWizardAction.setNeedsRestart(true);
            }
        }

        protected void performTask(Task task) throws PackageException {
            ValidationStatus validationStatus = task.validate();
            if (validationStatus.hasErrors()) {
                throw new PackageException("Failed to validate package " + task.getPackage().getId() + " -> "
                        + validationStatus.getErrors());
            }
            if (validationStatus.hasWarnings()) {
                log.warn("Got warnings on package validation " + task.getPackage().getId() + " -> "
                        + validationStatus.getWarnings());
            }
            task.run(null);
        }
    }

    protected void setStatus(SnapshotStatus status, String errorMessage) {
        studioSnapshotStatus = status;
        studioSnapshotUpdateError = errorMessage;
    }

    protected void setStatus(SnapshotStatus status, String errorMessage, ValidationStatus validationStatus) {
        setStatus(status, errorMessage);
        setStudioSnapshotValidationStatus(validationStatus);
    }

    /**
     * @since 5.7.1
     */
    public ValidationStatus getStudioSnapshotValidationStatus() {
        return studioSnapshotValidationStatus;
    }

    /**
     * @since 5.7.1
     */
    public void setStudioSnapshotValidationStatus(ValidationStatus status) {
        studioSnapshotValidationStatus = status;
    }

    /**
     * @since 5.7.1
     */
    public void resetStudioSnapshotValidationStatus() {
        setStudioSnapshotValidationStatus(null);
    }

    public void setDevMode(boolean value) {
        String feedbackCompId = "changeDevModeForm";
        ConfigurationGenerator conf = setupWizardAction.getConfigurationGenerator();
        boolean configurable = conf.isConfigurable();
        if (!configurable) {
            facesMessages.addToControl(feedbackCompId, StatusMessage.Severity.ERROR,
                    translate("label.setup.nuxeo.org.nuxeo.dev.changingDevModeNotConfigurable"));
            return;
        }
        Map<String, String> params = new HashMap<>();
        params.put(Framework.NUXEO_DEV_SYSTEM_PROP, Boolean.toString(value));
        try {
            conf.saveFilteredConfiguration(params);
            conf.getServerConfigurator().dumpProperties(conf.getUserConfig());
            // force reload of framework properties to ensure it's immediately
            // taken into account by all code checking for
            // Framework#isDevModeSet
            Framework.getRuntime().reloadProperties();

            if (value) {
                facesMessages.addToControl(feedbackCompId, StatusMessage.Severity.WARN,
                        translate("label.admin.center.devMode.justActivated"));
            } else {
                facesMessages.addToControl(feedbackCompId, StatusMessage.Severity.INFO,
                        translate("label.admin.center.devMode.justDisabled"));
            }
        } catch (ConfigurationException | IOException e) {
            log.error(e, e);
            facesMessages.addToControl(feedbackCompId, StatusMessage.Severity.ERROR,
                    translate("label.admin.center.devMode.errorSaving", e.getMessage()));
        } finally {
            setupWizardAction.setNeedsRestart(true);
            setupWizardAction.resetParameters();
            Contexts.getEventContext().remove("nxDevModeSet");
        }
    }
}