001/*
002 * (C) Copyright 2006-2014 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 */
020package org.nuxeo.connect.client.we;
021
022import java.util.ArrayList;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Map;
026import java.util.Map.Entry;
027
028import javax.ws.rs.GET;
029import javax.ws.rs.POST;
030import javax.ws.rs.Path;
031import javax.ws.rs.PathParam;
032import javax.ws.rs.Produces;
033import javax.ws.rs.QueryParam;
034
035import org.apache.commons.logging.Log;
036import org.apache.commons.logging.LogFactory;
037import org.nuxeo.connect.client.vindoz.InstallAfterRestart;
038import org.nuxeo.connect.packages.PackageManager;
039import org.nuxeo.connect.packages.dependencies.DependencyResolution;
040import org.nuxeo.connect.packages.dependencies.TargetPlatformFilterHelper;
041import org.nuxeo.connect.update.LocalPackage;
042import org.nuxeo.connect.update.Package;
043import org.nuxeo.connect.update.PackageException;
044import org.nuxeo.connect.update.PackageUpdateService;
045import org.nuxeo.connect.update.ValidationStatus;
046import org.nuxeo.connect.update.Version;
047import org.nuxeo.connect.update.model.Field;
048import org.nuxeo.connect.update.model.Form;
049import org.nuxeo.connect.update.task.Task;
050import org.nuxeo.ecm.admin.runtime.PlatformVersionHelper;
051import org.nuxeo.ecm.core.api.NuxeoException;
052import org.nuxeo.ecm.webengine.forms.FormData;
053import org.nuxeo.ecm.webengine.model.WebObject;
054import org.nuxeo.ecm.webengine.model.impl.DefaultObject;
055import org.nuxeo.runtime.api.Framework;
056
057/**
058 * Provides REST bindings for {@link Package} install management.
059 *
060 * @author <a href="mailto:td@nuxeo.com">Thierry Delprat</a>
061 */
062@WebObject(type = "installHandler")
063public class InstallHandler extends DefaultObject {
064
065    protected static final Log log = LogFactory.getLog(InstallHandler.class);
066
067    protected static final String INSTALL_PARAM_MAPS = "org.nuxeo.connect.updates.install.params";
068
069    protected String getStorageKey(String pkgId) {
070        return INSTALL_PARAM_MAPS + "_" + pkgId;
071    }
072
073    @SuppressWarnings("unchecked")
074    protected Map<String, String> getInstallParameters(String pkgId) {
075        Map<String, String> params = (Map<String, String>) getContext().getRequest().getAttribute(getStorageKey(pkgId));
076        if (params == null) {
077            params = new HashMap<>();
078        }
079        return params;
080    }
081
082    protected void storeInstallParameters(String pkgId, Map<String, String> params) {
083        getContext().getRequest().setAttribute(getStorageKey(pkgId), params);
084    }
085
086    protected void clearInstallParameters(String pkgId) {
087        getContext().getRequest().setAttribute(getStorageKey(pkgId), null);
088    }
089
090    @GET
091    @Produces("text/html")
092    @Path(value = "showTermsAndConditions/{pkgId}")
093    public Object showTermsAndConditions(@PathParam("pkgId") String pkgId, @QueryParam("source") String source,
094            @QueryParam("depCheck") Boolean depCheck) {
095        if (depCheck == null) {
096            depCheck = true;
097        }
098        try {
099            PackageUpdateService pus = Framework.getLocalService(PackageUpdateService.class);
100            LocalPackage pkg = pus.getPackage(pkgId);
101            String content = pkg.getTermsAndConditionsContent();
102            return getView("termsAndConditions").arg("pkg", pkg).arg("source", source).arg("content", content).arg(
103                    "depCheck", depCheck);
104        } catch (PackageException e) {
105            log.error("Error during terms and conditions phase ", e);
106            return getView("installError").arg("e", e).arg("source", source);
107        }
108    }
109
110    @GET
111    @Produces("text/html")
112    @Path(value = "start/{pkgId}")
113    public Object startInstall(@PathParam("pkgId") String pkgId, @QueryParam("source") String source,
114            @QueryParam("tacAccepted") Boolean acceptedTAC, @QueryParam("depCheck") Boolean depCheck,
115            @QueryParam("autoMode") Boolean autoMode) {
116        try {
117            PackageUpdateService pus = Framework.getLocalService(PackageUpdateService.class);
118            LocalPackage pkg = pus.getPackage(pkgId);
119            if (pkg == null) {
120                throw new NuxeoException("Can not find package " + pkgId);
121            }
122            if (pkg.requireTermsAndConditionsAcceptance() && !Boolean.TRUE.equals(acceptedTAC)) {
123                return showTermsAndConditions(pkgId, source, depCheck);
124            }
125            if (!Boolean.FALSE.equals(depCheck)) {
126                // check deps requirements
127                if (pkg.getDependencies() != null && pkg.getDependencies().length > 0) {
128                    PackageManager pm = Framework.getLocalService(PackageManager.class);
129                    DependencyResolution resolution = pm.resolveDependencies(pkgId,
130                            PlatformVersionHelper.getPlatformFilter());
131                    if (resolution.isFailed() && PlatformVersionHelper.getPlatformFilter() != null) {
132                        // retry without PF filter ...
133                        resolution = pm.resolveDependencies(pkgId, null);
134                    }
135                    if (resolution.isFailed()) {
136                        return getView("dependencyError").arg("resolution", resolution).arg("pkg", pkg).arg("source",
137                                source);
138                    } else {
139                        if (resolution.requireChanges()) {
140                            if (autoMode == null) {
141                                autoMode = true;
142                            }
143                            return getView("displayDependencies").arg("resolution", resolution).arg("pkg", pkg).arg(
144                                    "source", source).arg("autoMode", autoMode);
145                        }
146                        // no dep changes => can continue standard install
147                        // process
148                    }
149                }
150            }
151            Task installTask = pkg.getInstallTask();
152            ValidationStatus status = installTask.validate();
153            String targetPlatform = PlatformVersionHelper.getPlatformFilter();
154            if (!TargetPlatformFilterHelper.isCompatibleWithTargetPlatform(pkg, targetPlatform)) {
155                status.addWarning("This package is not validated for you current platform: " + targetPlatform);
156            }
157            if (status.hasErrors()) {
158                return getView("canNotInstall").arg("status", status).arg("pkg", pkg).arg("source", source);
159            }
160
161            boolean needWizard = false;
162            Form[] forms = installTask.getPackage().getInstallForms();
163            if (forms != null && forms.length > 0) {
164                needWizard = true;
165            }
166            return getView("startInstall").arg("status", status).arg("needWizard", needWizard).arg("installTask",
167                    installTask).arg("pkg", pkg).arg("source", source);
168        } catch (PackageException e) {
169            log.error("Error during first step of installation", e);
170            return getView("installError").arg("e", e).arg("source", source);
171        }
172    }
173
174    @GET
175    @Produces("text/html")
176    @Path(value = "form/{pkgId}/{formId}")
177    public Object showInstallForm(@PathParam("pkgId") String pkgId, @PathParam("formId") int formId,
178            @QueryParam("source") String source) {
179        PackageUpdateService pus = Framework.getLocalService(PackageUpdateService.class);
180        try {
181            LocalPackage pkg = pus.getPackage(pkgId);
182            Task installTask = pkg.getInstallTask();
183            Form[] forms = installTask.getPackage().getInstallForms();
184            if (forms == null || forms.length < formId - 1) {
185                return getView("installError").arg("e",
186                        new NuxeoException("No form with Id " + formId + " for package " + pkgId)).arg("source",
187                        source);
188            }
189            return getView("showInstallForm").arg("form", forms[formId]).arg("pkg", pkg).arg("source", source).arg(
190                    "step", formId + 1).arg("steps", forms.length);
191        } catch (PackageException e) {
192            log.error("Error during displaying Form nb " + formId, e);
193            return getView("installError").arg("e", e).arg("source", source);
194        }
195    }
196
197    @POST
198    @Produces("text/html")
199    @Path(value = "form/{pkgId}/{formId}")
200    public Object processInstallForm(@PathParam("pkgId") String pkgId, @PathParam("formId") int formId,
201            @QueryParam("source") String source) {
202        PackageUpdateService pus = Framework.getLocalService(PackageUpdateService.class);
203        try {
204            LocalPackage pkg = pus.getPackage(pkgId);
205            Task installTask = pkg.getInstallTask();
206            Form[] forms = installTask.getPackage().getInstallForms();
207            if (forms == null || forms.length < formId - 1) {
208                return getView("installError").arg("e",
209                        new NuxeoException("No form with Id " + formId + " for package " + pkgId)).arg("source",
210                        source);
211            }
212
213            Form form = forms[formId];
214            FormData fdata = getContext().getForm();
215            Map<String, String> params = getInstallParameters(pkgId);
216            for (Field field : form.getFields()) {
217                String data = fdata.getString(field.getName());
218                if (data != null) {
219                    params.put(field.getName(), data);
220                }
221                // XXX validation, and type checking ...
222            }
223            storeInstallParameters(pkgId, params);
224            if (formId + 1 == forms.length) {
225                // this was the last form screen : start the install
226                return doInstall(pkgId, source);
227            } else {
228                return showInstallForm(pkgId, formId + 1, source);
229            }
230        } catch (PackageException e) {
231            log.error("Error during processing Form nb " + formId, e);
232            return getView("installError").arg("e", e).arg("source", source);
233        }
234    }
235
236    @GET
237    @Produces("text/html")
238    @Path(value = "bulkRun/{pkgId}")
239    public Object doBulkInstall(@PathParam("pkgId") String pkgId, @QueryParam("source") String source,
240            @QueryParam("confirm") Boolean confirm) {
241        if (!RequestHelper.isInternalLink(getContext())) {
242            return getView("installError").arg("e",
243                    new NuxeoException("Installation seems to have been started from an external link.")).arg(
244                    "source", source);
245        }
246        PackageManager pm = Framework.getLocalService(PackageManager.class);
247        PackageUpdateService pus = Framework.getLocalService(PackageUpdateService.class);
248        try {
249            DependencyResolution resolution = pm.resolveDependencies(pkgId, PlatformVersionHelper.getPlatformFilter());
250            if (resolution.isFailed() && PlatformVersionHelper.getPlatformFilter() != null) {
251                // retry without PF filter ...
252                resolution = pm.resolveDependencies(pkgId, null);
253            }
254            List<String> downloadPackagesIds = resolution.getDownloadPackageIds();
255            if (downloadPackagesIds.size() > 0) {
256                return getView("installError").arg("e",
257                        new NuxeoException("Some packages need to be downloaded before running bulk installation")).arg(
258                        "source", source);
259            }
260
261            List<String> pkgIds = resolution.getOrderedPackageIdsToInstall();
262            List<String> warns = new ArrayList<>();
263            List<String> descs = new ArrayList<>();
264            if (!pkgIds.contains(pkgId)) {
265                pkgIds.add(pkgId);
266            }
267            List<String> rmPkgIds = new ArrayList<>();
268            for (Entry<String, Version> rmEntry : resolution.getLocalPackagesToRemove().entrySet()) {
269                String id = rmEntry.getKey() + "-" + rmEntry.getValue().toString();
270                rmPkgIds.add(id);
271            }
272            for (String id : pkgIds) {
273                Package pkg = pus.getPackage(id);
274                if (pkg == null) {
275                    return getView("installError").arg("e", new NuxeoException("Unable to find local package " + id)).arg(
276                            "source", source);
277                }
278                String targetPlatform = PlatformVersionHelper.getPlatformFilter();
279                if (!TargetPlatformFilterHelper.isCompatibleWithTargetPlatform(pkg, targetPlatform)) {
280                    warns.add("Package " + id + " is not validated for your current platform: " + targetPlatform);
281                }
282                descs.add(pkg.getDescription());
283            }
284            if (confirm != null && true == confirm) {
285                for (String id : rmPkgIds) {
286                    InstallAfterRestart.addPackageForUnInstallation(id);
287                }
288                for (String id : pkgIds) {
289                    InstallAfterRestart.addPackageForInstallation(id);
290                }
291                return getView("bulkInstallOnRestart").arg("pkgIds", pkgIds).arg("rmPkgIds", rmPkgIds).arg("source",
292                        source);
293            } else {
294                return getView("bulkInstallOnRestartConfirm").arg("pkgIds", pkgIds).arg("rmPkgIds", rmPkgIds).arg(
295                        "warns", warns).arg("descs", descs).arg("source", source).arg("pkgId", pkgId);
296            }
297        } catch (PackageException e) {
298            log.error("Error during installation of " + pkgId, e);
299            return getView("installError").arg("e", e).arg("source", source);
300        }
301    }
302
303    @GET
304    @Produces("text/html")
305    @Path(value = "run/{pkgId}")
306    public Object doInstall(@PathParam("pkgId") String pkgId, @QueryParam("source") String source) {
307        if (!RequestHelper.isInternalLink(getContext())) {
308            return getView("installError").arg("e",
309                    new NuxeoException("Installation seems to have been started from an external link.")).arg(
310                    "source", source);
311        }
312        PackageUpdateService pus = Framework.getLocalService(PackageUpdateService.class);
313        try {
314            LocalPackage pkg = pus.getPackage(pkgId);
315            if (InstallAfterRestart.isNeededForPackage(pkg)) {
316                InstallAfterRestart.addPackageForInstallation(pkg.getId());
317                return getView("installOnRestart").arg("pkg", pkg).arg("source", source);
318            }
319            Task installTask = pkg.getInstallTask();
320            Map<String, String> params = getInstallParameters(pkgId);
321            try {
322                installTask.run(params);
323            } catch (PackageException e) {
324                log.error("Error during installation of " + pkgId, e);
325                installTask.rollback();
326                return getView("installError").arg("e", e).arg("source", source);
327            }
328            clearInstallParameters(pkgId);
329            return getView("installedOK").arg("installTask", installTask).arg("pkg", pkg).arg("source", source);
330        } catch (PackageException e) {
331            log.error("Error during installation of " + pkgId, e);
332            return getView("installError").arg("e", e).arg("source", source);
333        }
334    }
335
336}