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}