001/*
002 * (C) Copyright 2006-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 - initial API and implementation
018 *
019 */
020
021package org.nuxeo.connect.client.jsf;
022
023import java.io.IOException;
024import java.io.Serializable;
025import java.nio.file.attribute.FileTime;
026import java.text.DateFormat;
027import java.text.SimpleDateFormat;
028import java.util.ArrayList;
029import java.util.Date;
030import java.util.HashMap;
031import java.util.List;
032import java.util.Locale;
033import java.util.Map;
034import java.util.TimeZone;
035import java.util.concurrent.TimeUnit;
036import java.util.stream.Stream;
037
038import javax.faces.context.FacesContext;
039import javax.faces.model.SelectItem;
040
041import org.apache.commons.lang3.ArrayUtils;
042import org.apache.commons.logging.Log;
043import org.apache.commons.logging.LogFactory;
044import org.jboss.seam.ScopeType;
045import org.jboss.seam.annotations.In;
046import org.jboss.seam.annotations.Name;
047import org.jboss.seam.annotations.Scope;
048import org.jboss.seam.contexts.Contexts;
049import org.jboss.seam.faces.FacesMessages;
050import org.jboss.seam.international.StatusMessage;
051import org.nuxeo.common.utils.ExceptionUtils;
052import org.nuxeo.connect.client.ui.SharedPackageListingsSettings;
053import org.nuxeo.connect.client.vindoz.InstallAfterRestart;
054import org.nuxeo.connect.client.we.StudioSnapshotHelper;
055import org.nuxeo.connect.connector.ConnectServerError;
056import org.nuxeo.connect.connector.http.ConnectUrlConfig;
057import org.nuxeo.connect.data.DownloadablePackage;
058import org.nuxeo.connect.data.DownloadingPackage;
059import org.nuxeo.connect.packages.PackageManager;
060import org.nuxeo.connect.packages.dependencies.DependencyResolution;
061import org.nuxeo.connect.packages.dependencies.TargetPlatformFilterHelper;
062import org.nuxeo.connect.update.LocalPackage;
063import org.nuxeo.connect.update.PackageDependency;
064import org.nuxeo.connect.update.PackageException;
065import org.nuxeo.connect.update.PackageState;
066import org.nuxeo.connect.update.PackageType;
067import org.nuxeo.connect.update.PackageUpdateService;
068import org.nuxeo.connect.update.ValidationStatus;
069import org.nuxeo.connect.update.task.Task;
070import org.nuxeo.ecm.admin.AdminViewManager;
071import org.nuxeo.ecm.admin.runtime.PlatformVersionHelper;
072import org.nuxeo.ecm.admin.runtime.ReloadHelper;
073import org.nuxeo.ecm.admin.setup.SetupWizardActionBean;
074import org.nuxeo.ecm.core.api.NuxeoException;
075import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils;
076import org.nuxeo.ecm.webapp.seam.NuxeoSeamHotReloadContextKeeper;
077import org.nuxeo.launcher.config.ConfigurationException;
078import org.nuxeo.launcher.config.ConfigurationGenerator;
079import org.nuxeo.runtime.api.Framework;
080import org.nuxeo.runtime.reload.ReloadService;
081import org.nuxeo.runtime.services.config.ConfigurationService;
082import org.nuxeo.runtime.util.Watch;
083import org.nuxeo.runtime.util.Watch.TimeInterval;
084
085/**
086 * Manages JSF views for Package Management.
087 *
088 * @author <a href="mailto:td@nuxeo.com">Thierry Delprat</a>
089 */
090@Name("appsViews")
091@Scope(ScopeType.CONVERSATION)
092public class AppCenterViewsManager implements Serializable {
093
094    private static final long serialVersionUID = 1L;
095
096    protected static final Log log = LogFactory.getLog(AppCenterViewsManager.class);
097
098    private static final String LABEL_STUDIO_UPDATE_STATUS = "label.studio.update.status.";
099
100    /**
101     * FIXME JC: should follow or simply reuse {@link PackageState}
102     */
103    protected enum SnapshotStatus {
104        downloading, saving, installing, error, completed, restartNeeded;
105    }
106
107    protected static final Map<String, String> view2PackageListName = new HashMap<String, String>() {
108        private static final long serialVersionUID = 1L;
109        {
110            put("ConnectAppsUpdates", "updates");
111            put("ConnectAppsStudio", "studio");
112            put("ConnectAppsRemote", "remote");
113            put("ConnectAppsLocal", "local");
114        }
115    };
116
117    @In(create = true)
118    protected String currentAdminSubViewId;
119
120    @In(create = true)
121    protected NuxeoSeamHotReloadContextKeeper seamReloadContext;
122
123    @In(create = true)
124    protected SetupWizardActionBean setupWizardAction;
125
126    @In(create = true, required = false)
127    protected FacesMessages facesMessages;
128
129    @In(create = true)
130    protected Map<String, String> messages;
131
132    protected String searchString;
133
134    protected SnapshotStatus studioSnapshotStatus;
135
136    protected int studioSnapshotDownloadProgress;
137
138    protected boolean isStudioSnapshopUpdateInProgress = false;
139
140    protected String studioSnapshotUpdateError;
141
142    /**
143     * Boolean indicating is Studio snapshot package validation should be done.
144     *
145     * @since 5.7.1
146     */
147    protected Boolean validateStudioSnapshot;
148
149    /**
150     * Last validation status of the Studio snapshot package
151     *
152     * @since 5.7.1
153     */
154    protected ValidationStatus studioSnapshotValidationStatus;
155
156    private FileTime lastUpdate = null;
157
158    protected DownloadablePackage studioSnapshotPackage;
159
160    /**
161     * Using a dedicated property because studioSnapshotPackage might be null.
162     *
163     * @since 7.10
164     */
165    protected Boolean studioSnapshotPackageCached = false;
166
167    public String getSearchString() {
168        if (searchString == null) {
169            return "";
170        }
171        return searchString;
172    }
173
174    public void setSearchString(String searchString) {
175        this.searchString = searchString;
176    }
177
178    public boolean getOnlyRemote() {
179        return SharedPackageListingsSettings.instance().get("remote").isOnlyRemote();
180    }
181
182    public void setOnlyRemote(boolean onlyRemote) {
183        SharedPackageListingsSettings.instance().get("remote").setOnlyRemote(onlyRemote);
184    }
185
186    protected String getListName() {
187        return view2PackageListName.get(currentAdminSubViewId);
188    }
189
190    public void setPlatformFilter(boolean doFilter) {
191        SharedPackageListingsSettings.instance().get(getListName()).setPlatformFilter(doFilter);
192    }
193
194    public boolean getPlatformFilter() {
195        return SharedPackageListingsSettings.instance().get(getListName()).getPlatformFilter();
196    }
197
198    public String getPackageTypeFilter() {
199        return SharedPackageListingsSettings.instance().get(getListName()).getPackageTypeFilter();
200    }
201
202    public void setPackageTypeFilter(String filter) {
203        SharedPackageListingsSettings.instance().get(getListName()).setPackageTypeFilter(filter);
204    }
205
206    public List<SelectItem> getPackageTypes() {
207        List<SelectItem> types = new ArrayList<>();
208        SelectItem allItem = new SelectItem("", "label.packagetype.all");
209        types.add(allItem);
210        for (PackageType ptype : PackageType.values()) {
211            // if (!ptype.equals(PackageType.STUDIO)) {
212            SelectItem item = new SelectItem(ptype.getValue(), "label.packagetype." + ptype.getValue());
213            types.add(item);
214            // }
215        }
216        return types;
217    }
218
219    public void flushCache() {
220        PackageManager pm = Framework.getService(PackageManager.class);
221        pm.flushCache();
222    }
223
224    /**
225     * Method binding for the update button: needs to perform a real redirection (as ajax context is broken after hot
226     * reload) and to provide an outcome so that redirection through the URL service goes ok (even if it just reset its
227     * navigation handler cache).
228     *
229     * @since 5.6
230     */
231    public String installStudioSnapshotAndRedirect() {
232        installStudioSnapshot();
233        return AdminViewManager.VIEW_ADMIN;
234    }
235
236    public void installStudioSnapshot() {
237        if (isStudioSnapshopUpdateInProgress) {
238            return;
239        }
240        PackageManager pm = Framework.getService(PackageManager.class);
241        // TODO NXP-16228: should directly request the SNAPSHOT package (if only we knew its name!)
242        List<DownloadablePackage> pkgs = pm.listRemoteAssociatedStudioPackages();
243        DownloadablePackage snapshotPkg = StudioSnapshotHelper.getSnapshot(pkgs);
244        studioSnapshotUpdateError = null;
245        resetStudioSnapshotValidationStatus();
246        if (snapshotPkg != null) {
247            isStudioSnapshopUpdateInProgress = true;
248            try {
249                StudioAutoInstaller studioAutoInstaller = new StudioAutoInstaller(pm, snapshotPkg.getId(),
250                        shouldValidateStudioSnapshot());
251                studioAutoInstaller.run();
252            } finally {
253                isStudioSnapshopUpdateInProgress = false;
254            }
255        } else {
256            studioSnapshotUpdateError = translate("label.studio.update.error.noSnapshotPackageFound");
257        }
258    }
259
260    public boolean isStudioSnapshopUpdateInProgress() {
261        return isStudioSnapshopUpdateInProgress;
262    }
263
264    /**
265     * Returns true if validation should be performed
266     *
267     * @since 5.7.1
268     */
269    public Boolean getValidateStudioSnapshot() {
270        return validateStudioSnapshot;
271    }
272
273    /**
274     * @since 5.7.1
275     */
276    public void setValidateStudioSnapshot(Boolean validateStudioSnapshot) {
277        this.validateStudioSnapshot = validateStudioSnapshot;
278    }
279
280    /**
281     * Returns true if Studio snapshot module should be validated.
282     * <p>
283     * Validation can be skipped by user, or can be globally disabled by setting framework property
284     * "studio.snapshot.disablePkgValidation" to true.
285     *
286     * @since 5.7.1
287     */
288    protected boolean shouldValidateStudioSnapshot() {
289        ConfigurationService cs = Framework.getService(ConfigurationService.class);
290        if (cs.isBooleanPropertyTrue("studio.snapshot.disablePkgValidation")) {
291            return false;
292        }
293        return Boolean.TRUE.equals(getValidateStudioSnapshot());
294    }
295
296    protected static String translate(String label, Object... params) {
297        return ComponentUtils.translate(FacesContext.getCurrentInstance(), label, params);
298    }
299
300    protected FileTime getLastUpdateDate() {
301        if (lastUpdate == null) {
302            DownloadablePackage snapshotPkg = getStudioProjectSnapshot();
303            if (snapshotPkg != null) {
304                PackageUpdateService pus = Framework.getService(PackageUpdateService.class);
305                try {
306                    LocalPackage pkg = pus.getPackage(snapshotPkg.getId());
307                    if (pkg != null) {
308                        lastUpdate = pus.getInstallDate(pkg.getId());
309                    }
310                } catch (PackageException e) {
311                    log.error(e);
312                }
313            }
314        }
315        return lastUpdate;
316    }
317
318    /**
319     * @since 7.10
320     */
321    public String getStudioUrl() {
322        return ConnectUrlConfig.getStudioUrl(getSnapshotStudioProjectName());
323    }
324
325    /**
326     * @since 7.10
327     */
328    public DownloadablePackage getStudioProjectSnapshot() {
329        if (!studioSnapshotPackageCached) {
330            PackageManager pm = Framework.getService(PackageManager.class);
331            // TODO NXP-16228: should directly request the SNAPSHOT package (if only we knew its name!)
332            List<DownloadablePackage> pkgs = pm.listRemoteAssociatedStudioPackages();
333            studioSnapshotPackage = StudioSnapshotHelper.getSnapshot(pkgs);
334            studioSnapshotPackageCached = true;
335        }
336        return studioSnapshotPackage;
337    }
338
339    /**
340     * @return null if there is no SNAPSHOT package
341     * @since 7.10
342     */
343    public String getSnapshotStudioProjectName() {
344        DownloadablePackage snapshotPkg = getStudioProjectSnapshot();
345        if (snapshotPkg != null) {
346            return snapshotPkg.getName();
347        }
348        return null;
349    }
350
351    public String getStudioInstallationStatus() {
352        if (studioSnapshotStatus == null) {
353            LocalPackage pkg = null;
354            DownloadablePackage snapshotPkg = getStudioProjectSnapshot();
355            if (snapshotPkg != null) {
356                try {
357                    PackageUpdateService pus = Framework.getService(PackageUpdateService.class);
358                    pkg = pus.getPackage(snapshotPkg.getId());
359                } catch (PackageException e) {
360                    log.error(e);
361                }
362            }
363            if (pkg == null) {
364                return translate(LABEL_STUDIO_UPDATE_STATUS + "noStatus");
365            }
366            PackageState studioPkgState = pkg.getPackageState();
367            if (studioPkgState == PackageState.DOWNLOADING) {
368                studioSnapshotStatus = SnapshotStatus.downloading;
369            } else if (studioPkgState == PackageState.DOWNLOADED) {
370                studioSnapshotStatus = SnapshotStatus.saving;
371            } else if (studioPkgState == PackageState.INSTALLING) {
372                studioSnapshotStatus = SnapshotStatus.installing;
373            } else if (studioPkgState.isInstalled()) {
374                studioSnapshotStatus = SnapshotStatus.completed;
375            } else {
376                studioSnapshotStatus = SnapshotStatus.error;
377            }
378        }
379
380        Object[] params = new Object[0];
381        if (SnapshotStatus.error.equals(studioSnapshotStatus)) {
382            if (studioSnapshotUpdateError == null) {
383                studioSnapshotUpdateError = "???";
384            }
385            params = new Object[] { studioSnapshotUpdateError };
386        } else if (SnapshotStatus.downloading.equals(studioSnapshotStatus)) {
387            params = new Object[] { String.valueOf(studioSnapshotDownloadProgress) };
388        } else {
389            FileTime update = getLastUpdateDate();
390            if (update != null) {
391                DateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
392                df.setTimeZone(TimeZone.getDefault());
393                params = new Object[] { df.format(new Date(update.toMillis())) };
394            }
395        }
396
397        return translate(LABEL_STUDIO_UPDATE_STATUS + studioSnapshotStatus.name(), params);
398    }
399
400    // TODO: plug a notifier for status to be shown to the user
401    protected class StudioAutoInstaller implements Runnable {
402
403        protected final String packageId;
404
405        protected final PackageManager pm;
406
407        /**
408         * @since 5.7.1
409         */
410        protected final boolean validate;
411
412        protected StudioAutoInstaller(PackageManager pm, String packageId, boolean validate) {
413            this.pm = pm;
414            this.packageId = packageId;
415            this.validate = validate;
416        }
417
418        @Override
419        public void run() {
420            if (validate) {
421                ValidationStatus status = new ValidationStatus();
422
423                pm.flushCache();
424                DownloadablePackage remotePkg = pm.findRemotePackageById(packageId);
425                if (remotePkg == null) {
426                    status.addError(String.format("Cannot perform validation: remote package '%s' not found", packageId));
427                    return;
428                }
429
430                PackageDependency[] pkgDeps = remotePkg.getDependencies();
431                if (log.isDebugEnabled()) {
432                    log.debug(String.format("%s target platforms: %s", remotePkg,
433                            ArrayUtils.toString(remotePkg.getTargetPlatforms())));
434                    log.debug(String.format("%s dependencies: %s", remotePkg, ArrayUtils.toString(pkgDeps)));
435                }
436
437                // TODO NXP-11776: replace errors by internationalized labels
438                String targetPlatform = PlatformVersionHelper.getPlatformFilter();
439                if (!TargetPlatformFilterHelper.isCompatibleWithTargetPlatform(remotePkg, targetPlatform)) {
440                    status.addError(String.format("This package is not validated for your current platform: %s",
441                            targetPlatform));
442                }
443                // check deps requirements
444                if (pkgDeps != null && pkgDeps.length > 0) {
445                    DependencyResolution resolution = pm.resolveDependencies(packageId, targetPlatform);
446                    if (resolution.isFailed() && targetPlatform != null) {
447                        // retry without PF filter in case it gives more information
448                        resolution = pm.resolveDependencies(packageId, null);
449                    }
450                    if (resolution.isFailed()) {
451                        status.addError(String.format("Dependency check has failed for package '%s' (%s)", packageId,
452                                resolution));
453                    } else {
454                        List<String> pkgToInstall = resolution.getInstallPackageIds();
455                        if (pkgToInstall != null && pkgToInstall.size() == 1 && packageId.equals(pkgToInstall.get(0))) {
456                            // ignore
457                        } else if (resolution.requireChanges()) {
458                            // do not install needed deps: they may not be hot-reloadable and that's not what the
459                            // "update snapshot" button is for.
460                            status.addError(resolution.toString().trim().replaceAll("\n", "<br />"));
461                        }
462                    }
463                }
464
465                if (status.hasErrors()) {
466                    setStatus(SnapshotStatus.error, translate("label.studio.update.validation.error"), status);
467                    return;
468                }
469            }
470
471            // Effective install
472            if (Framework.isDevModeSet()) {
473                hotReloadPackage();
474            } else {
475                InstallAfterRestart.addPackageForInstallation(packageId);
476                setStatus(SnapshotStatus.restartNeeded, null);
477                setupWizardAction.setNeedsRestart(true);
478            }
479        }
480
481        private void hotReloadPackage() {
482            Watch watch = new Watch().start();
483            boolean useCompatReload = Framework.isBooleanPropertyTrue(ReloadService.USE_COMPAT_HOT_RELOAD);
484
485            PackageUpdateService pus = Framework.getService(PackageUpdateService.class);
486            try {
487                if (!useCompatReload) {
488                    try {
489                        setStatus(SnapshotStatus.installing, null);
490                        log.info("Use hot reload update mechanism");
491                        ReloadHelper.hotReloadPackage(packageId);
492                        // Refresh state
493                        lastUpdate = pus.getInstallDate(packageId);
494                        setStatus(SnapshotStatus.completed, null);
495                        return;
496                    } catch (NuxeoException e) {
497                        log.error("Error while updating studio snapshot", e);
498                        Throwable cause = e.getCause();
499                        if (cause instanceof ConnectServerError) {
500                            setStatus(SnapshotStatus.error, e.getMessage());
501                        } else if (cause instanceof PackageException) {
502                            setStatus(SnapshotStatus.error, translate("label.studio.update.installation.error", e.getMessage()));
503                        } else if (cause instanceof InterruptedException) {
504                            setStatus(SnapshotStatus.error, translate("label.studio.update.downloading.error", e.getMessage()));
505                            Thread.currentThread().interrupt();
506                            throw e;
507                        }
508                    }
509                }
510                try {
511                    LocalPackage pkg = pus.getPackage(packageId);
512
513                    // Uninstall and/or remove if needed
514                    if (pkg != null) {
515                        watch.start("uninstall");
516                        log.info(String.format("Removing package %s before update...", pkg));
517                        if (pkg.getPackageState().isInstalled()) {
518                            // First remove it to allow SNAPSHOT upgrade
519                            log.info("Uninstalling " + packageId);
520                            Task uninstallTask = pkg.getUninstallTask();
521                            try {
522                                performTask(uninstallTask);
523                            } catch (PackageException e) {
524                                uninstallTask.rollback();
525                                throw e;
526                            }
527                        }
528                        pus.removePackage(packageId);
529                        watch.stop("uninstall");
530                    }
531
532                    // Download
533                    watch.start("download");
534                    setStatus(SnapshotStatus.downloading, null);
535                    DownloadingPackage downloadingPkg = pm.download(packageId);
536                    while (!downloadingPkg.isCompleted()) {
537                        studioSnapshotDownloadProgress = downloadingPkg.getDownloadProgress();
538                        log.debug("downloading studio snapshot package");
539                        Thread.sleep(100);
540                    }
541                    studioSnapshotDownloadProgress = downloadingPkg.getDownloadProgress();
542                    setStatus(SnapshotStatus.saving, null);
543                    watch.stop("download");
544
545                    // Install
546                    watch.start("install");
547                    setStatus(SnapshotStatus.installing, null);
548                    log.info("Installing " + packageId);
549                    pkg = pus.getPackage(packageId);
550                    if (pkg == null || PackageState.DOWNLOADED != pkg.getPackageState()) {
551                        log.error("Error while downloading studio snapshot " + pkg);
552                        setStatus(SnapshotStatus.error, translate("label.studio.update.downloading.error", pkg));
553                        return;
554                    }
555                    Task installTask = pkg.getInstallTask();
556                    try {
557                        performTask(installTask);
558                    } catch (PackageException e) {
559                        installTask.rollback();
560                        throw e;
561                    }
562                    // Refresh state
563                    pkg = pus.getPackage(packageId);
564                    lastUpdate = pus.getInstallDate(packageId);
565                    setStatus(SnapshotStatus.completed, null);
566                    watch.stop("install");
567                } catch (ConnectServerError e) {
568                    setStatus(SnapshotStatus.error, e.getMessage());
569                } catch (InterruptedException e) {
570                    log.error("Error while downloading studio snapshot", e);
571                    setStatus(SnapshotStatus.error, translate("label.studio.update.downloading.error", e.getMessage()));
572                    ExceptionUtils.checkInterrupt(e);
573                } catch (PackageException e) {
574                    log.error("Error while installing studio snapshot", e);
575                    setStatus(SnapshotStatus.error, translate("label.studio.update.installation.error", e.getMessage()));
576                }
577            } finally {
578                watch.stop();
579                if (log.isInfoEnabled()) {
580                    StringBuilder message = new StringBuilder();
581                    message.append("Hot reload has been done in ")
582                           .append(watch.getTotal().elapsed(TimeUnit.MILLISECONDS))
583                           .append(" ms, detailed steps:");
584                    Stream.of(watch.getIntervals()).filter(TimeInterval::isStopped).forEach(
585                            i -> message.append("\n- ")
586                                        .append(i.getName())
587                                        .append(": ")
588                                        .append(i.elapsed(TimeUnit.MILLISECONDS))
589                                        .append(" ms"));
590                    log.info(message.toString());
591                }
592            }
593        }
594
595        protected void performTask(Task task) throws PackageException {
596            ValidationStatus validationStatus = task.validate();
597            if (validationStatus.hasErrors()) {
598                throw new PackageException("Failed to validate package " + task.getPackage().getId() + " -> "
599                        + validationStatus.getErrors());
600            }
601            if (validationStatus.hasWarnings()) {
602                log.warn("Got warnings on package validation " + task.getPackage().getId() + " -> "
603                        + validationStatus.getWarnings());
604            }
605            task.run(null);
606        }
607    }
608
609    protected void setStatus(SnapshotStatus status, String errorMessage) {
610        studioSnapshotStatus = status;
611        studioSnapshotUpdateError = errorMessage;
612    }
613
614    protected void setStatus(SnapshotStatus status, String errorMessage, ValidationStatus validationStatus) {
615        setStatus(status, errorMessage);
616        setStudioSnapshotValidationStatus(validationStatus);
617    }
618
619    /**
620     * @since 5.7.1
621     */
622    public ValidationStatus getStudioSnapshotValidationStatus() {
623        return studioSnapshotValidationStatus;
624    }
625
626    /**
627     * @since 5.7.1
628     */
629    public void setStudioSnapshotValidationStatus(ValidationStatus status) {
630        studioSnapshotValidationStatus = status;
631    }
632
633    /**
634     * @since 5.7.1
635     */
636    public void resetStudioSnapshotValidationStatus() {
637        setStudioSnapshotValidationStatus(null);
638    }
639
640    public void setDevMode(boolean value) {
641        String feedbackCompId = "changeDevModeForm";
642        ConfigurationGenerator conf = setupWizardAction.getConfigurationGenerator();
643        boolean configurable = conf.isConfigurable();
644        if (!configurable) {
645            facesMessages.addToControl(feedbackCompId, StatusMessage.Severity.ERROR,
646                    translate("label.setup.nuxeo.org.nuxeo.dev.changingDevModeNotConfigurable"));
647            return;
648        }
649        Map<String, String> params = new HashMap<>();
650        params.put(Framework.NUXEO_DEV_SYSTEM_PROP, Boolean.toString(value));
651        try {
652            conf.saveFilteredConfiguration(params);
653            conf.getServerConfigurator().dumpProperties(conf.getUserConfig());
654            // force reload of framework properties to ensure it's immediately
655            // taken into account by all code checking for
656            // Framework#isDevModeSet
657            Framework.getRuntime().reloadProperties();
658
659            if (value) {
660                facesMessages.addToControl(feedbackCompId, StatusMessage.Severity.WARN,
661                        translate("label.admin.center.devMode.justActivated"));
662            } else {
663                facesMessages.addToControl(feedbackCompId, StatusMessage.Severity.INFO,
664                        translate("label.admin.center.devMode.justDisabled"));
665            }
666        } catch (ConfigurationException | IOException e) {
667            log.error(e, e);
668            facesMessages.addToControl(feedbackCompId, StatusMessage.Severity.ERROR,
669                    translate("label.admin.center.devMode.errorSaving", e.getMessage()));
670        } finally {
671            setupWizardAction.setNeedsRestart(true);
672            setupWizardAction.resetParameters();
673            Contexts.getEventContext().remove("nxDevModeSet");
674        }
675    }
676}