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