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