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:[email protected]">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.getService(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.getService(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.getService(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.getService(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.getService(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.getService(PackageManager.class); 249 PackageUpdateService pus = Framework.getService(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.getService(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}