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