001/* 002 * (C) Copyright 2012-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 * Julien Carsique 016 * Mathieu Guillaume 017 * 018 */ 019 020package org.nuxeo.launcher.connect; 021 022import java.io.Console; 023import java.io.File; 024import java.io.IOException; 025import java.io.InputStream; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.Date; 029import java.util.HashMap; 030import java.util.List; 031import java.util.Map; 032import java.util.SortedMap; 033import java.util.TreeMap; 034import java.util.zip.ZipEntry; 035import java.util.zip.ZipException; 036import java.util.zip.ZipFile; 037 038import javax.xml.parsers.DocumentBuilder; 039import javax.xml.parsers.DocumentBuilderFactory; 040import javax.xml.xpath.XPath; 041import javax.xml.xpath.XPathConstants; 042import javax.xml.xpath.XPathExpression; 043import javax.xml.xpath.XPathFactory; 044 045import org.apache.commons.collections.ListUtils; 046import org.apache.commons.io.FileUtils; 047import org.apache.commons.lang.ArrayUtils; 048import org.apache.commons.lang.StringUtils; 049import org.apache.commons.logging.Log; 050import org.apache.commons.logging.LogFactory; 051import org.apache.commons.logging.impl.SimpleLog; 052import org.w3c.dom.Document; 053import org.w3c.dom.NodeList; 054 055import org.nuxeo.common.Environment; 056import org.nuxeo.connect.CallbackHolder; 057import org.nuxeo.connect.NuxeoConnectClient; 058import org.nuxeo.connect.connector.ConnectServerError; 059import org.nuxeo.connect.data.DownloadablePackage; 060import org.nuxeo.connect.data.DownloadingPackage; 061import org.nuxeo.connect.identity.LogicalInstanceIdentifier; 062import org.nuxeo.connect.identity.LogicalInstanceIdentifier.InvalidCLID; 063import org.nuxeo.connect.identity.LogicalInstanceIdentifier.NoCLID; 064import org.nuxeo.connect.packages.PackageManager; 065import org.nuxeo.connect.packages.dependencies.CUDFHelper; 066import org.nuxeo.connect.packages.dependencies.DependencyResolution; 067import org.nuxeo.connect.update.LocalPackage; 068import org.nuxeo.connect.update.Package; 069import org.nuxeo.connect.update.PackageException; 070import org.nuxeo.connect.update.PackageState; 071import org.nuxeo.connect.update.PackageType; 072import org.nuxeo.connect.update.PackageUtils; 073import org.nuxeo.connect.update.PackageVisibility; 074import org.nuxeo.connect.update.ValidationStatus; 075import org.nuxeo.connect.update.Version; 076import org.nuxeo.connect.update.model.PackageDefinition; 077import org.nuxeo.connect.update.standalone.StandaloneUpdateService; 078import org.nuxeo.connect.update.task.Task; 079import org.nuxeo.launcher.info.CommandInfo; 080import org.nuxeo.launcher.info.CommandSetInfo; 081import org.nuxeo.launcher.info.PackageInfo; 082 083/** 084 * @since 5.6 085 */ 086public class ConnectBroker { 087 088 private static final Log log = LogFactory.getLog(ConnectBroker.class); 089 090 private static final int PACKAGES_DOWNLOAD_TIMEOUT_SECONDS = 300; 091 092 public static final String PARAM_MP_DIR = "nuxeo.distribution.marketplace.dir"; 093 094 public static final String DISTRIBUTION_MP_DIR_DEFAULT = "setupWizardDownloads"; 095 096 public static final String PACKAGES_XML = "packages.xml"; 097 098 protected static final String LAUNCHER_CHANGED_PROPERTY = "launcher.changed"; 099 100 protected static final int LAUNCHER_CHANGED_EXIT_CODE = 128; 101 102 public static final String[] POSITIVE_ANSWERS = { "true", "yes", "y" }; 103 104 private Environment env; 105 106 private StandaloneUpdateService service; 107 108 private CallbackHolder cbHolder; 109 110 private CommandSetInfo cset = new CommandSetInfo(); 111 112 private String targetPlatform; 113 114 private String distributionMPDir; 115 116 private String relax = OPTION_RELAX_DEFAULT; 117 118 public static final String OPTION_RELAX_DEFAULT = "ask"; 119 120 private String accept = OPTION_ACCEPT_DEFAULT; 121 122 private boolean allowSNAPSHOT = CUDFHelper.defaultAllowSNAPSHOT; 123 124 public static final String OPTION_ACCEPT_DEFAULT = "ask"; 125 126 public ConnectBroker(Environment env) throws IOException, PackageException { 127 this.env = env; 128 service = new StandaloneUpdateService(env); 129 service.initialize(); 130 cbHolder = new StandaloneCallbackHolder(env, service); 131 NuxeoConnectClient.setCallBackHolder(cbHolder); 132 targetPlatform = env.getProperty(Environment.DISTRIBUTION_NAME) + "-" 133 + env.getProperty(Environment.DISTRIBUTION_VERSION); 134 distributionMPDir = env.getProperty(PARAM_MP_DIR, DISTRIBUTION_MP_DIR_DEFAULT); 135 } 136 137 public String getCLID() throws NoCLID { 138 return LogicalInstanceIdentifier.instance().getCLID(); 139 } 140 141 /** 142 * @throws NoCLID 143 * @since 6.0 144 */ 145 public void setCLID(String file) throws NoCLID { 146 try { 147 LogicalInstanceIdentifier.load(file); 148 } catch (IOException | InvalidCLID e) { 149 throw new NoCLID("can not load CLID", e); 150 } 151 } 152 153 public StandaloneUpdateService getUpdateService() { 154 return service; 155 } 156 157 public PackageManager getPackageManager() { 158 return NuxeoConnectClient.getPackageManager(); 159 } 160 161 public void refreshCache() { 162 getPackageManager().flushCache(); 163 NuxeoConnectClient.getPackageManager().listAllPackages(); 164 } 165 166 public CommandSetInfo getCommandSet() { 167 return cset; 168 } 169 170 protected LocalPackage getInstalledPackage(String pkgName) { 171 try { 172 return service.getPersistence().getActivePackage(pkgName); 173 } catch (PackageException e) { 174 log.error(e); 175 return null; 176 } 177 } 178 179 protected boolean isInstalledPackage(String pkgName) { 180 return service.getPersistence().getActivePackageId(pkgName) != null; 181 } 182 183 protected boolean isLocalPackageId(String pkgId) { 184 try { 185 return service.getPackage(pkgId) != null; 186 } catch (PackageException e) { 187 log.error("Error looking for local package " + pkgId, e); 188 return false; 189 } 190 } 191 192 protected boolean isRemotePackageId(String pkgId) { 193 return PackageUtils.isValidPackageId(pkgId) 194 && NuxeoConnectClient.getPackageManager().findPackageById(pkgId) != null; 195 } 196 197 protected String getBestIdForNameInList(String pkgName, List<? extends Package> pkgList) { 198 String foundId = null; 199 SortedMap<Version, String> foundPkgs = new TreeMap<>(); 200 SortedMap<Version, String> matchingPkgs = new TreeMap<>(); 201 for (Package pkg : pkgList) { 202 if (pkg.getName().equals(pkgName)) { 203 foundPkgs.put(pkg.getVersion(), pkg.getId()); 204 if (Arrays.asList(pkg.getTargetPlatforms()).contains(targetPlatform)) { 205 matchingPkgs.put(pkg.getVersion(), pkg.getId()); 206 } 207 } 208 } 209 if (matchingPkgs.size() != 0) { 210 foundId = matchingPkgs.get(matchingPkgs.lastKey()); 211 } else if (foundPkgs.size() != 0) { 212 foundId = foundPkgs.get(foundPkgs.lastKey()); 213 } 214 return foundId; 215 216 } 217 218 protected String getLocalPackageIdFromName(String pkgName) { 219 return getBestIdForNameInList(pkgName, getPkgList()); 220 } 221 222 protected List<String> getAllLocalPackageIdsFromName(String pkgName) { 223 List<String> foundIds = new ArrayList<>(); 224 for (Package pkg : getPkgList()) { 225 if (pkg.getName().equals(pkgName)) { 226 foundIds.add(pkg.getId()); 227 } 228 } 229 return foundIds; 230 } 231 232 protected String getInstalledPackageIdFromName(String pkgName) { 233 List<LocalPackage> localPackages = getPkgList(); 234 List<LocalPackage> installedPackages = new ArrayList<>(); 235 for (LocalPackage pkg : localPackages) { 236 if (pkg.getPackageState().isInstalled()) { 237 installedPackages.add(pkg); 238 } 239 } 240 return getBestIdForNameInList(pkgName, installedPackages); 241 } 242 243 protected String getRemotePackageIdFromName(String pkgName) { 244 return getBestIdForNameInList(pkgName, NuxeoConnectClient.getPackageManager().findRemotePackages(pkgName)); 245 } 246 247 /** 248 * Looks for a remote package from its name or id 249 * 250 * @param pkgNameOrId 251 * @return the remote package Id; null if not found 252 * @since 5.7 253 */ 254 protected String getRemotePackageId(String pkgNameOrId) { 255 String pkgId = null; 256 if (isRemotePackageId(pkgNameOrId)) { 257 // Check whether this is a remote package ID 258 pkgId = pkgNameOrId; 259 } else { 260 // Check whether this is a remote package name 261 pkgId = getRemotePackageIdFromName(pkgNameOrId); 262 } 263 return pkgId; 264 } 265 266 /** 267 * Looks for a local package from its name or id 268 * 269 * @since 5.7 270 * @param pkgIdOrName 271 * @return the local package Id; null if not found 272 * @throws PackageException 273 */ 274 protected LocalPackage getLocalPackage(String pkgIdOrName) throws PackageException { 275 // Try as a package id 276 LocalPackage pkg = service.getPackage(pkgIdOrName); 277 if (pkg == null) { 278 // Check whether this is the name of a local package 279 String pkgId = getLocalPackageIdFromName(pkgIdOrName); 280 if (pkgId != null) { 281 pkg = service.getPackage(pkgId); 282 } 283 } 284 return pkg; 285 } 286 287 /** 288 * Looks for a package file from its path 289 * 290 * @param pkgFile Absolute or relative package file path 291 * @return the file if found, else null 292 */ 293 protected File getLocalPackageFile(String pkgFile) { 294 if (pkgFile.startsWith("file:")) { 295 pkgFile = pkgFile.substring(5); 296 } 297 // Try absolute path 298 File fileToCheck = new File(pkgFile); 299 if (!fileToCheck.exists()) { // Try relative path 300 fileToCheck = new File(env.getServerHome(), pkgFile); 301 } 302 if (fileToCheck.exists()) { 303 return fileToCheck; 304 } else { 305 return null; 306 } 307 } 308 309 protected boolean isLocalPackageFile(String pkgFile) { 310 return (getLocalPackageFile(pkgFile) != null); 311 } 312 313 protected List<String> getDistributionFilenames() { 314 File distributionMPFile = new File(distributionMPDir, PACKAGES_XML); 315 List<String> md5Filenames = new ArrayList<>(); 316 // Try to get md5 files from packages.xml 317 DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); 318 docFactory.setNamespaceAware(true); 319 try { 320 DocumentBuilder builder = docFactory.newDocumentBuilder(); 321 Document doc = builder.parse(distributionMPFile); 322 XPathFactory xpFactory = XPathFactory.newInstance(); 323 XPath xpath = xpFactory.newXPath(); 324 XPathExpression expr = xpath.compile("//package/@md5"); 325 NodeList nodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); 326 for (int i = 0; i < nodes.getLength(); i++) { 327 String md5 = nodes.item(i).getNodeValue(); 328 if ((md5 != null) && (md5.length() > 0)) { 329 md5Filenames.add(md5); 330 } 331 } 332 } catch (Exception e) { 333 // Parsing failed - return empty list 334 log.error("Failed parsing " + distributionMPFile, e); 335 return new ArrayList<>(); 336 } 337 return md5Filenames; 338 } 339 340 protected Map<String, PackageDefinition> getDistributionDefinitions(List<String> md5Filenames) { 341 Map<String, PackageDefinition> allDefinitions = new HashMap<>(); 342 if (md5Filenames == null) { 343 return allDefinitions; 344 } 345 for (String md5Filename : md5Filenames) { 346 File md5File = new File(distributionMPDir, md5Filename); 347 if (!md5File.exists()) { 348 // distribution file has been deleted 349 continue; 350 } 351 ZipFile zipFile; 352 try { 353 zipFile = new ZipFile(md5File); 354 } catch (ZipException e) { 355 log.warn("Unzip error reading file " + md5File, e); 356 continue; 357 } catch (IOException e) { 358 log.warn("Could not read file " + md5File, e); 359 continue; 360 } 361 try { 362 ZipEntry zipEntry = zipFile.getEntry("package.xml"); 363 InputStream in = zipFile.getInputStream(zipEntry); 364 PackageDefinition pd = NuxeoConnectClient.getPackageUpdateService().loadPackage(in); 365 allDefinitions.put(md5Filename, pd); 366 } catch (Exception e) { 367 log.error("Could not read package description", e); 368 continue; 369 } finally { 370 try { 371 zipFile.close(); 372 } catch (IOException e) { 373 log.warn("Unexpected error closing file " + md5File, e); 374 } 375 } 376 } 377 return allDefinitions; 378 } 379 380 protected boolean addDistributionPackage(String md5) { 381 boolean ret = true; 382 File distributionFile = new File(distributionMPDir, md5); 383 if (distributionFile.exists()) { 384 try { 385 ret = pkgAdd(distributionFile.getCanonicalPath()) != null; 386 } catch (IOException e) { 387 log.warn("Could not add distribution file " + md5); 388 ret = false; 389 } 390 } 391 return ret; 392 } 393 394 public boolean addDistributionPackages() { 395 Map<String, PackageDefinition> distributionPackages = getDistributionDefinitions(getDistributionFilenames()); 396 if (distributionPackages.isEmpty()) { 397 return true; 398 } 399 List<LocalPackage> localPackages = getPkgList(); 400 Map<String, LocalPackage> localPackagesById = new HashMap<>(); 401 if (localPackages != null) { 402 for (LocalPackage pkg : localPackages) { 403 localPackagesById.put(pkg.getId(), pkg); 404 } 405 } 406 boolean ret = true; 407 for (String md5 : distributionPackages.keySet()) { 408 PackageDefinition md5Pkg = distributionPackages.get(md5); 409 if (localPackagesById.containsKey(md5Pkg.getId())) { 410 // We have the same package Id in the local cache 411 LocalPackage localPackage = localPackagesById.get(md5Pkg.getId()); 412 if (localPackage.getVersion().isSnapshot()) { 413 // - For snapshots, until we have timestamp support, assume 414 // distribution version is newer than cached version. 415 // - This may (will) break the server if there are 416 // dependencies/compatibility changes or if the package is 417 // in installed state. 418 if (!localPackage.getPackageState().isInstalled()) { 419 pkgRemove(localPackage.getId()); 420 ret = addDistributionPackage(md5) && ret; 421 } 422 } 423 } else { 424 // No package with this Id is in cache 425 ret = addDistributionPackage(md5) && ret; 426 } 427 } 428 return ret; 429 } 430 431 public List<LocalPackage> getPkgList() { 432 try { 433 return service.getPackages(); 434 } catch (PackageException e) { 435 log.error("Could not read package list", e); 436 return null; 437 } 438 } 439 440 public void pkgList() { 441 log.info("Local packages:"); 442 pkgList(getPkgList()); 443 } 444 445 public void pkgListAll() { 446 log.info("All packages:"); 447 pkgList(NuxeoConnectClient.getPackageManager().listAllPackages()); 448 } 449 450 public void pkgList(List<? extends Package> packagesList) { 451 CommandInfo cmdInfo = cset.newCommandInfo(CommandInfo.CMD_LIST); 452 try { 453 if (packagesList.isEmpty()) { 454 log.info("None"); 455 } else { 456 NuxeoConnectClient.getPackageManager().sort(packagesList); 457 StringBuilder sb = new StringBuilder(); 458 for (Package pkg : packagesList) { 459 newPackageInfo(cmdInfo, pkg); 460 PackageState packageState = pkg.getPackageState(); 461 String packageDescription = packageState.getLabel(); 462 packageDescription = String.format("%6s %11s\t", pkg.getType(), packageDescription); 463 if (packageState == PackageState.REMOTE && pkg.getType() != PackageType.STUDIO 464 && pkg.getVisibility() != PackageVisibility.PUBLIC 465 && !LogicalInstanceIdentifier.isRegistered()) { 466 packageDescription += "Registration required for "; 467 } 468 packageDescription += String.format("%s (id: %s)\n", pkg.getName(), pkg.getId()); 469 sb.append(packageDescription); 470 } 471 log.info(sb.toString()); 472 } 473 } catch (Exception e) { 474 log.error(e); 475 cmdInfo.exitCode = 1; 476 } 477 } 478 479 protected void performTask(Task task) throws PackageException { 480 ValidationStatus validationStatus = task.validate(); 481 if (validationStatus.hasErrors()) { 482 throw new PackageException("Failed to validate package " + task.getPackage().getId() + " -> " 483 + validationStatus.getErrors()); 484 } 485 if (validationStatus.hasWarnings()) { 486 log.warn("Got warnings on package validation " + task.getPackage().getId() + " -> " 487 + validationStatus.getWarnings()); 488 } 489 task.run(null); 490 } 491 492 public boolean pkgReset() { 493 CommandInfo cmdInfo = cset.newCommandInfo(CommandInfo.CMD_RESET); 494 if ("ask".equalsIgnoreCase(accept)) { 495 accept = readConsole("The reset will erase Marketplace packages history.\n" 496 + "Do you want to continue (yes/no)? [yes] ", "yes"); 497 } 498 if (!Boolean.parseBoolean(accept)) { 499 cmdInfo.exitCode = 1; 500 return false; 501 } 502 try { 503 service.reset(); 504 log.info("Packages reset done: all packages were marked as DOWNLOADED"); 505 List<LocalPackage> localPackages = service.getPackages(); 506 for (LocalPackage localPackage : localPackages) { 507 localPackage.getUninstallFile().delete(); 508 FileUtils.deleteDirectory(localPackage.getData().getEntry(LocalPackage.BACKUP_DIR)); 509 newPackageInfo(cmdInfo, localPackage); 510 } 511 service.getRegistry().delete(); 512 FileUtils.deleteDirectory(service.getBackupDir()); 513 } catch (PackageException e) { 514 log.error(e); 515 cmdInfo.exitCode = 1; 516 } catch (IOException e) { 517 log.error(e); 518 cmdInfo.exitCode = 1; 519 } 520 return cmdInfo.exitCode == 0; 521 } 522 523 public boolean pkgPurge() throws PackageException { 524 List<String> localNames = new ArrayList<>(); 525 // Remove packages in DOWNLOADED state first 526 // This will avoid extending the CUDF universe needlessly 527 for (LocalPackage pkg : service.getPackages()) { 528 if (pkg.getPackageState() == PackageState.DOWNLOADED) { 529 pkgRemove(pkg.getId()); 530 } 531 } 532 // Process the remaining packages 533 for (LocalPackage pkg : service.getPackages()) { 534 localNames.add(pkg.getName()); 535 } 536 return pkgRequest(null, null, null, localNames); 537 } 538 539 /** 540 * Uninstall a list of packages. If the list contains a package name (versus an ID), only the considered as best 541 * matching package is uninstalled. 542 * 543 * @param packageIdsToRemove The list can contain package IDs and names 544 * @see #pkgUninstall(String) 545 */ 546 public boolean pkgUninstall(List<String> packageIdsToRemove) { 547 log.debug("Uninstalling: " + packageIdsToRemove); 548 for (String pkgId : packageIdsToRemove) { 549 if (pkgUninstall(pkgId) == null) { 550 log.error("Unable to uninstall " + pkgId); 551 return false; 552 } 553 } 554 return true; 555 } 556 557 /** 558 * Uninstall a local package. The package is not removed from cache. 559 * 560 * @param pkgId Package ID or Name 561 * @return The uninstalled LocalPackage or null if failed 562 */ 563 public LocalPackage pkgUninstall(String pkgId) { 564 if (env.getProperty(LAUNCHER_CHANGED_PROPERTY, "false").equals("true")) { 565 System.exit(LAUNCHER_CHANGED_EXIT_CODE); 566 } 567 CommandInfo cmdInfo = cset.newCommandInfo(CommandInfo.CMD_UNINSTALL); 568 cmdInfo.param = pkgId; 569 try { 570 LocalPackage pkg = service.getPackage(pkgId); 571 if (pkg == null) { 572 // Check whether this is the name of an installed package 573 String realPkgId = getInstalledPackageIdFromName(pkgId); 574 if (realPkgId != null) { 575 pkgId = realPkgId; 576 pkg = service.getPackage(realPkgId); 577 } 578 } 579 if (pkg == null) { 580 throw new PackageException("Package not found: " + pkgId); 581 } 582 log.info("Uninstalling " + pkgId); 583 Task uninstallTask = pkg.getUninstallTask(); 584 try { 585 performTask(uninstallTask); 586 } catch (PackageException e) { 587 uninstallTask.rollback(); 588 throw e; 589 } 590 // Refresh state 591 pkg = service.getPackage(pkgId); 592 newPackageInfo(cmdInfo, pkg); 593 return pkg; 594 } catch (Exception e) { 595 log.error("Failed to uninstall package: " + pkgId, e); 596 cmdInfo.exitCode = 1; 597 return null; 598 } 599 } 600 601 /** 602 * Remove a list of packages from cache. If the list contains a package name (versus an ID), all matching packages 603 * are removed. 604 * 605 * @param pkgsToRemove The list can contain package IDs and names 606 * @see #pkgRemove(String) 607 */ 608 public boolean pkgRemove(List<String> pkgsToRemove) { 609 boolean cmdOk = true; 610 if (pkgsToRemove != null) { 611 log.debug("Removing: " + pkgsToRemove); 612 for (String pkgNameOrId : pkgsToRemove) { 613 List<String> allIds; 614 if (isLocalPackageId(pkgNameOrId)) { 615 allIds = new ArrayList<>(); 616 allIds.add(pkgNameOrId); 617 } else { 618 // Request made on a name: remove all matching packages 619 allIds = getAllLocalPackageIdsFromName(pkgNameOrId); 620 } 621 for (String pkgId : allIds) { 622 if (pkgRemove(pkgId) == null) { 623 log.warn("Unable to remove " + pkgId); 624 // Don't error out on failed (cache) removal 625 cmdOk = false; 626 } 627 } 628 } 629 } 630 return cmdOk; 631 } 632 633 /** 634 * Remove a package from cache. If it was installed, the package is uninstalled then removed. 635 * 636 * @param pkgId Package ID or Name 637 * @return The removed LocalPackage or null if failed 638 */ 639 public LocalPackage pkgRemove(String pkgId) { 640 CommandInfo cmdInfo = cset.newCommandInfo(CommandInfo.CMD_REMOVE); 641 cmdInfo.param = pkgId; 642 try { 643 LocalPackage pkg = service.getPackage(pkgId); 644 if (pkg == null) { 645 // Check whether this is the name of a local package 646 String realPkgId = getLocalPackageIdFromName(pkgId); 647 if (realPkgId != null) { 648 pkgId = realPkgId; 649 pkg = service.getPackage(realPkgId); 650 } 651 } 652 if (pkg == null) { 653 throw new PackageException("Package not found: " + pkgId); 654 } 655 if (pkg.getPackageState().isInstalled()) { 656 pkgUninstall(pkgId); 657 // Refresh state 658 pkg = service.getPackage(pkgId); 659 } 660 if (pkg.getPackageState() != PackageState.DOWNLOADED) { 661 throw new PackageException("Can only remove packages in DOWNLOADED, INSTALLED or STARTED state"); 662 } 663 service.removePackage(pkgId); 664 log.info("Removed " + pkgId); 665 newPackageInfo(cmdInfo, pkg).state = PackageState.REMOTE; 666 return pkg; 667 } catch (Exception e) { 668 log.error("Failed to remove package: " + pkgId, e); 669 cmdInfo.exitCode = 1; 670 return null; 671 } 672 } 673 674 /** 675 * Add a list of packages into the cache, downloading them if needed and possible. 676 * 677 * @param pkgsToAdd 678 * @return true if command succeeded 679 * @see #pkgAdd(String) 680 */ 681 public boolean pkgAdd(List<String> pkgsToAdd) { 682 return pkgAdd(pkgsToAdd, false); 683 } 684 685 /** 686 * Add a list of packages into the cache, downloading them if needed and possible. 687 * 688 * @since 6.0 689 * @param pkgsToAdd 690 * @param ignoreMissing 691 * @return true if command succeeded 692 * @see #pkgAdd(String) 693 */ 694 public boolean pkgAdd(List<String> pkgsToAdd, boolean ignoreMissing) { 695 boolean cmdOk = true; 696 if (pkgsToAdd == null || pkgsToAdd.isEmpty()) { 697 return cmdOk; 698 } 699 List<String> pkgIdsToDownload = new ArrayList<>(); 700 for (String pkgToAdd : pkgsToAdd) { 701 CommandInfo cmdInfo = cset.newCommandInfo(CommandInfo.CMD_ADD); 702 cmdInfo.param = pkgToAdd; 703 try { 704 File fileToAdd = getLocalPackageFile(pkgToAdd); 705 if (fileToAdd == null) { 706 String pkgId = getRemotePackageId(pkgToAdd); 707 if (pkgId == null) { 708 if (ignoreMissing) { 709 log.warn("Could not add package: " + pkgToAdd); 710 cmdInfo.newMessage(SimpleLog.LOG_LEVEL_INFO, "Could not add package: " + pkgToAdd); 711 } else { 712 throw new PackageException("Could not find a remote or local (relative to " 713 + "current directory or to NUXEO_HOME) " + "package with name or ID " + pkgToAdd); 714 } 715 } else { 716 cmdInfo.newMessage(SimpleLog.LOG_LEVEL_INFO, "Waiting for download"); 717 pkgIdsToDownload.add(pkgId); 718 } 719 } else { 720 LocalPackage pkg = service.addPackage(fileToAdd); 721 log.info("Added " + pkg); 722 newPackageInfo(cmdInfo, pkg); 723 } 724 } catch (PackageException e) { 725 cmdOk = false; 726 cmdInfo.exitCode = 1; 727 cmdInfo.newMessage(e); 728 } 729 } 730 cmdOk = downloadPackages(pkgIdsToDownload) && cmdOk; 731 return cmdOk; 732 } 733 734 /** 735 * Add a package file into the cache 736 * 737 * @param packageFileName 738 * @return The added LocalPackage or null if failed 739 */ 740 public LocalPackage pkgAdd(String packageFileName) { 741 return pkgAdd(packageFileName, false); 742 } 743 744 /** 745 * Add a package file into the cache 746 * 747 * @since 6.0 748 * @param packageFileName 749 * @param ignoreMissing 750 * @return The added LocalPackage or null if failed 751 */ 752 public LocalPackage pkgAdd(String packageFileName, boolean ignoreMissing) { 753 CommandInfo cmdInfo = cset.newCommandInfo(CommandInfo.CMD_ADD); 754 cmdInfo.param = packageFileName; 755 LocalPackage pkg = null; 756 try { 757 File fileToAdd = getLocalPackageFile(packageFileName); 758 if (fileToAdd == null) { 759 String pkgId = getRemotePackageId(packageFileName); 760 if (pkgId == null) { 761 if (ignoreMissing) { 762 log.warn("Could not add package: " + packageFileName); 763 cmdInfo.newMessage(SimpleLog.LOG_LEVEL_INFO, "Could not add package: " + packageFileName); 764 return null; 765 } else { 766 throw new PackageException("Could not find a remote or local (relative to " 767 + "current directory or to NUXEO_HOME) " + "package with name or ID " + packageFileName); 768 } 769 } else if (!downloadPackages(Arrays.asList(new String[] { pkgId }))) { 770 throw new PackageException("Could not download package " + pkgId); 771 } 772 pkg = service.getPackage(pkgId); 773 if (pkg == null) { 774 throw new PackageException("Could not find downloaded package in cache " + pkgId); 775 } 776 } else { 777 pkg = service.addPackage(fileToAdd); 778 } 779 log.info("Added " + packageFileName); 780 newPackageInfo(cmdInfo, pkg); 781 } catch (PackageException e) { 782 cmdInfo.exitCode = 1; 783 cmdInfo.newMessage(e); 784 } 785 return pkg; 786 } 787 788 /** 789 * Install a list of local packages. If the list contains a package name (versus an ID), only the considered as best 790 * matching package is installed. 791 * 792 * @param packageIdsToInstall The list can contain package IDs and names 793 * @see #pkgInstall(String) 794 */ 795 public boolean pkgInstall(List<String> packageIdsToInstall) { 796 return pkgInstall(packageIdsToInstall, false); 797 } 798 799 /** 800 * Install a list of local packages. If the list contains a package name (versus an ID), only the considered as best 801 * matching package is installed. 802 * 803 * @since 6.0 804 * @param packageIdsToInstall The list can contain package IDs and names 805 * @param ignoreMissing If true, doesn't throw an exception on unknown packages 806 * @see #pkgInstall(String) 807 */ 808 public boolean pkgInstall(List<String> packageIdsToInstall, boolean ignoreMissing) { 809 log.debug("Installing: " + packageIdsToInstall); 810 for (String pkgId : packageIdsToInstall) { 811 if (pkgInstall(pkgId, ignoreMissing) == null && !ignoreMissing) { 812 return false; 813 } 814 } 815 return true; 816 } 817 818 /** 819 * Install a local package. 820 * 821 * @param pkgId Package ID or Name 822 * @return The installed LocalPackage or null if failed 823 */ 824 public LocalPackage pkgInstall(String pkgId) { 825 return pkgInstall(pkgId, false); 826 } 827 828 /** 829 * Install a local package. 830 * 831 * @since 6.0 832 * @param pkgId Package ID or Name 833 * @param ignoreMissing If true, doesn't throw an exception on unknown packages 834 * @return The installed LocalPackage or null if failed 835 */ 836 public LocalPackage pkgInstall(String pkgId, boolean ignoreMissing) { 837 if (env.getProperty(LAUNCHER_CHANGED_PROPERTY, "false").equals("true")) { 838 System.exit(LAUNCHER_CHANGED_EXIT_CODE); 839 } 840 CommandInfo cmdInfo = cset.newCommandInfo(CommandInfo.CMD_INSTALL); 841 cmdInfo.param = pkgId; 842 try { 843 LocalPackage pkg = getLocalPackage(pkgId); 844 if (pkg != null && pkg.getPackageState().isInstalled()) { 845 if (pkg.getVersion().isSnapshot()) { 846 log.info(String.format("Updating package %s...", pkg)); 847 // First remove it to allow SNAPSHOT upgrade 848 pkgRemove(pkgId); 849 pkg = null; 850 } else { 851 log.info(String.format("Package %s is already installed.", pkg)); 852 return pkg; 853 } 854 } 855 if (pkg == null) { 856 // We don't know this package, try to add it first 857 pkg = pkgAdd(pkgId, ignoreMissing); 858 } 859 if (pkg == null) { 860 // Nothing worked - can't find the package anywhere 861 if (ignoreMissing) { 862 log.warn("Unable to install package: " + pkgId); 863 return null; 864 } else { 865 throw new PackageException("Package not found: " + pkgId); 866 } 867 } 868 pkgId = pkg.getId(); 869 cmdInfo.param = pkgId; 870 log.info("Installing " + pkgId); 871 Task installTask = pkg.getInstallTask(); 872 try { 873 performTask(installTask); 874 } catch (PackageException e) { 875 installTask.rollback(); 876 throw e; 877 } 878 // Refresh state 879 pkg = service.getPackage(pkgId); 880 newPackageInfo(cmdInfo, pkg); 881 return pkg; 882 } catch (PackageException e) { 883 log.error(String.format("Failed to install package: %s (%s)", pkgId, e.getMessage())); 884 log.debug(e, e); 885 cmdInfo.exitCode = 1; 886 cmdInfo.newMessage(e); 887 return null; 888 } 889 } 890 891 public boolean listPending(File commandsFile) { 892 return executePending(commandsFile, false, false); 893 } 894 895 /** 896 * @since 5.6 897 * @param commandsFile File containing the commands to execute 898 * @param doExecute Whether to execute or list the actions 899 * @param useResolver Whether to use full resolution or just execute individual actions 900 */ 901 public boolean executePending(File commandsFile, boolean doExecute, boolean useResolver) { 902 int errorValue = 0; 903 if (!commandsFile.isFile()) { 904 return false; 905 } 906 List<String> pkgsToAdd = new ArrayList<>(); 907 List<String> pkgsToInstall = new ArrayList<>(); 908 List<String> pkgsToUninstall = new ArrayList<>(); 909 List<String> pkgsToRemove = new ArrayList<>(); 910 List<String> lines; 911 try { 912 lines = FileUtils.readLines(commandsFile); 913 for (String line : lines) { 914 line = line.trim(); 915 String[] split = line.split("\\s+", 2); 916 if (split.length == 2) { 917 if (split[0].equals(CommandInfo.CMD_INSTALL)) { 918 if (doExecute) { 919 if (useResolver) { 920 pkgsToInstall.add(split[1]); 921 } else { 922 pkgInstall(split[1]); 923 } 924 } else { 925 CommandInfo cmdInfo = cset.newCommandInfo(CommandInfo.CMD_INSTALL); 926 cmdInfo.param = split[1]; 927 cmdInfo.pending = true; 928 } 929 } else if (split[0].equals(CommandInfo.CMD_ADD)) { 930 if (doExecute) { 931 if (useResolver) { 932 pkgsToAdd.add(split[1]); 933 } else { 934 pkgAdd(split[1]); 935 } 936 } else { 937 CommandInfo cmdInfo = cset.newCommandInfo(CommandInfo.CMD_ADD); 938 cmdInfo.param = split[1]; 939 cmdInfo.pending = true; 940 } 941 } else if (split[0].equals(CommandInfo.CMD_UNINSTALL)) { 942 if (doExecute) { 943 if (useResolver) { 944 pkgsToUninstall.add(split[1]); 945 } else { 946 pkgUninstall(split[1]); 947 } 948 } else { 949 CommandInfo cmdInfo = cset.newCommandInfo(CommandInfo.CMD_UNINSTALL); 950 cmdInfo.param = split[1]; 951 cmdInfo.pending = true; 952 } 953 } else if (split[0].equals(CommandInfo.CMD_REMOVE)) { 954 if (doExecute) { 955 if (useResolver) { 956 pkgsToRemove.add(split[1]); 957 } else { 958 pkgRemove(split[1]); 959 } 960 } else { 961 CommandInfo cmdInfo = cset.newCommandInfo(CommandInfo.CMD_REMOVE); 962 cmdInfo.param = split[1]; 963 cmdInfo.pending = true; 964 } 965 } else { 966 errorValue = 1; 967 } 968 } else if (split.length == 1) { 969 if (line.length() > 0 && !line.startsWith("#")) { 970 if (doExecute) { 971 if ("init".equals(line)) { 972 if (!addDistributionPackages()) { 973 errorValue = 1; 974 } 975 } else { 976 if (useResolver) { 977 pkgsToInstall.add(line); 978 } else { 979 pkgInstall(line); 980 } 981 } 982 } else { 983 if ("init".equals(line)) { 984 CommandInfo cmdInfo = cset.newCommandInfo(CommandInfo.CMD_INIT); 985 cmdInfo.pending = true; 986 } else { 987 CommandInfo cmdInfo = cset.newCommandInfo(CommandInfo.CMD_INSTALL); 988 cmdInfo.param = line; 989 cmdInfo.pending = true; 990 } 991 } 992 } 993 } 994 if (errorValue != 0) { 995 log.error("Error processing pending package/command: " + line); 996 } 997 } 998 if (doExecute) { 999 if (useResolver) { 1000 String oldAccept = accept; 1001 String oldRelax = relax; 1002 accept = "true"; 1003 if ("ask".equalsIgnoreCase(relax)) { 1004 log.info("Relax mode changed from 'ask' to 'false' for executing the pending actions."); 1005 relax = "false"; 1006 } 1007 boolean success = pkgRequest(pkgsToAdd, pkgsToInstall, pkgsToUninstall, pkgsToRemove); 1008 accept = oldAccept; 1009 relax = oldRelax; 1010 if (!success) { 1011 errorValue = 2; 1012 } 1013 } 1014 if (errorValue != 0) { 1015 File bak = new File(commandsFile.getPath() + ".bak"); 1016 bak.delete(); 1017 commandsFile.renameTo(bak); 1018 } else { 1019 commandsFile.delete(); 1020 } 1021 } else { 1022 cset.log(true); 1023 } 1024 } catch (IOException e) { 1025 log.error(e.getMessage()); 1026 } 1027 return errorValue == 0; 1028 1029 } 1030 1031 @SuppressWarnings("unused") 1032 protected boolean downloadPackages(List<String> packagesToDownload) { 1033 List<String> packagesAlreadyDownloaded = new ArrayList<String>(); 1034 Map<String, String> packagesToRemove = new HashMap<String, String>(); 1035 for (String pkg : packagesToDownload) { 1036 try { 1037 LocalPackage localPackage = getLocalPackage(pkg); 1038 if (localPackage != null) { 1039 if (localPackage.getPackageState().isInstalled()) { 1040 log.error(String.format("Package %s is installed. Download skipped.", pkg)); 1041 packagesAlreadyDownloaded.add(pkg); 1042 } else if (localPackage.getVersion().isSnapshot()) { 1043 log.info(String.format("Download of %s will replace the one already in local cache", pkg)); 1044 packagesToRemove.put(localPackage.getId(), pkg); 1045 } else { 1046 log.info(String.format("Package %s is already in local cache", pkg)); 1047 packagesAlreadyDownloaded.add(pkg); 1048 } 1049 } 1050 } catch (PackageException e) { 1051 log.error(String.format("Looking for package %s in local cache raised an error. Aborting.", pkg), e); 1052 return false; 1053 } 1054 } 1055 1056 // First remove SNAPSHOT packages to replace 1057 for (String pkgToRemove : packagesToRemove.keySet()) { 1058 if (pkgRemove(pkgToRemove) == null) { 1059 log.error(String.format("Failed to remove %s. Download of %s skipped", pkgToRemove, 1060 packagesToRemove.get(pkgToRemove))); 1061 packagesToDownload.remove(packagesToRemove.get(pkgToRemove)); 1062 } 1063 } 1064 1065 packagesToDownload.removeAll(packagesAlreadyDownloaded); 1066 if (packagesToDownload.isEmpty()) { 1067 return true; 1068 } 1069 List<DownloadingPackage> pkgs = new ArrayList<DownloadingPackage>(); 1070 // Queue downloads 1071 log.info("Downloading " + packagesToDownload + "..."); 1072 boolean downloadOk = true; 1073 for (String pkg : packagesToDownload) { 1074 CommandInfo cmdInfo = cset.newCommandInfo(CommandInfo.CMD_DOWNLOAD); 1075 cmdInfo.param = pkg; 1076 try { 1077 DownloadingPackage download = getPackageManager().download(pkg); 1078 if (download != null) { 1079 pkgs.add(download); 1080 cmdInfo.param = download.getId(); 1081 cmdInfo.newMessage(SimpleLog.LOG_LEVEL_DEBUG, "Downloading " + download); 1082 } else { 1083 downloadOk = false; 1084 cmdInfo.exitCode = 1; 1085 cmdInfo.newMessage(SimpleLog.LOG_LEVEL_ERROR, 1086 String.format("Download failed for %s (not found)", pkg)); 1087 } 1088 } catch (ConnectServerError e) { 1089 log.debug(e, e); 1090 downloadOk = false; 1091 cmdInfo.exitCode = 1; 1092 cmdInfo.newMessage(SimpleLog.LOG_LEVEL_ERROR, 1093 String.format("Download failed for %s. %s", pkg, e.getMessage())); 1094 } 1095 } 1096 // Check progress 1097 long startTime = new Date().getTime(); 1098 long deltaTime = 0; 1099 do { 1100 List<DownloadingPackage> pkgsCompleted = new ArrayList<DownloadingPackage>(); 1101 for (DownloadingPackage pkg : pkgs) { 1102 if (pkg.isCompleted()) { 1103 pkgsCompleted.add(pkg); 1104 CommandInfo cmdInfo = cset.newCommandInfo(CommandInfo.CMD_DOWNLOAD); 1105 cmdInfo.param = pkg.getId(); 1106 // Digest check not correctly implemented 1107 if (false && !pkg.isDigestOk()) { 1108 downloadOk = false; 1109 cmdInfo.exitCode = 1; 1110 cmdInfo.newMessage(SimpleLog.LOG_LEVEL_ERROR, "Wrong digest for package " + pkg.getName()); 1111 } else if (pkg.getPackageState() == PackageState.DOWNLOADED) { 1112 cmdInfo.newMessage(SimpleLog.LOG_LEVEL_DEBUG, "Downloaded " + pkg); 1113 } else { 1114 downloadOk = false; 1115 cmdInfo.exitCode = 1; 1116 cmdInfo.newMessage(SimpleLog.LOG_LEVEL_ERROR, 1117 String.format("Download failed for %s. %s", pkg, pkg.getErrorMessage())); 1118 } 1119 } 1120 } 1121 pkgs.removeAll(pkgsCompleted); 1122 deltaTime = (new Date().getTime() - startTime) / 1000; 1123 } while (deltaTime < PACKAGES_DOWNLOAD_TIMEOUT_SECONDS && pkgs.size() > 0); 1124 // Timeout (not everything get downloaded)? 1125 if (pkgs.size() > 0) { 1126 downloadOk = false; 1127 log.error("Timeout while trying to download packages"); 1128 for (DownloadingPackage pkg : pkgs) { 1129 CommandInfo cmdInfo = cset.newCommandInfo(CommandInfo.CMD_ADD); 1130 cmdInfo.param = pkg.getId(); 1131 cmdInfo.exitCode = 1; 1132 cmdInfo.newMessage(SimpleLog.LOG_LEVEL_ERROR, "Download timeout for " + pkg); 1133 } 1134 } 1135 return downloadOk; 1136 } 1137 1138 public boolean pkgRequest(List<String> pkgsToAdd, List<String> pkgsToInstall, List<String> pkgsToUninstall, 1139 List<String> pkgsToRemove) { 1140 return pkgRequest(pkgsToAdd, pkgsToInstall, pkgsToUninstall, pkgsToRemove, true, false); 1141 } 1142 1143 /** 1144 * @param keepExisting If false, the request will remove existing packages that are not part of the resolution 1145 * @since 5.9.2 1146 */ 1147 public boolean pkgRequest(List<String> pkgsToAdd, List<String> pkgsToInstall, List<String> pkgsToUninstall, 1148 List<String> pkgsToRemove, boolean keepExisting) { 1149 return pkgRequest(pkgsToAdd, pkgsToInstall, pkgsToUninstall, pkgsToRemove, keepExisting, false); 1150 } 1151 1152 /** 1153 * @param keepExisting If false, the request will remove existing packages that are not part of the resolution 1154 * @param ignoreMissing Do not error out on missing packages, just handle the rest 1155 * @since 5.9.2 1156 */ 1157 public boolean pkgRequest(List<String> pkgsToAdd, List<String> pkgsToInstall, List<String> pkgsToUninstall, 1158 List<String> pkgsToRemove, boolean keepExisting, boolean ignoreMissing) { 1159 try { 1160 boolean cmdOk = true; 1161 // Add local files 1162 cmdOk = pkgAdd(pkgsToAdd, ignoreMissing); 1163 // Build solver request 1164 List<String> solverInstall = new ArrayList<>(); 1165 List<String> solverRemove = new ArrayList<>(); 1166 List<String> solverUpgrade = new ArrayList<>(); 1167 if (pkgsToInstall != null) { 1168 // If install request is a file name, add to cache and get the 1169 // id 1170 List<String> namesOrIdsToInstall = new ArrayList<>(); 1171 for (String pkgToInstall : pkgsToInstall) { 1172 if (isLocalPackageFile(pkgToInstall)) { 1173 LocalPackage addedPkg = pkgAdd(pkgToInstall); 1174 if (addedPkg != null) { 1175 namesOrIdsToInstall.add(addedPkg.getId()); 1176 } else { 1177 cmdOk = false; 1178 } 1179 // TODO: set flag to prefer local package 1180 } else { 1181 namesOrIdsToInstall.add(pkgToInstall); 1182 } 1183 } 1184 // Check whether we have new installs or upgrades 1185 Map<String, DownloadablePackage> allPackagesByID = NuxeoConnectClient.getPackageManager().getAllPackagesByID(); 1186 for (String pkgToInstall : namesOrIdsToInstall) { 1187 DownloadablePackage pkg = allPackagesByID.get(pkgToInstall); 1188 if (pkg != null) { 1189 // This is a known ID 1190 if (isInstalledPackage(pkg.getName())) { 1191 // The package is installed 1192 solverUpgrade.add(pkgToInstall); 1193 } else { 1194 // The package isn't installed yet 1195 solverInstall.add(pkgToInstall); 1196 } 1197 } else { 1198 // This is a name (or a non-existing ID) 1199 String id = getInstalledPackageIdFromName(pkgToInstall); 1200 if (id != null) { 1201 // The package is installed 1202 solverUpgrade.add(pkgToInstall); 1203 } else { 1204 // The package isn't installed yet 1205 solverInstall.add(pkgToInstall); 1206 } 1207 } 1208 } 1209 } 1210 if (pkgsToUninstall != null) { 1211 solverRemove.addAll(pkgsToUninstall); 1212 } 1213 if (pkgsToRemove != null) { 1214 // Add packages to remove to uninstall list 1215 solverRemove.addAll(pkgsToRemove); 1216 } 1217 if ((solverInstall.size() != 0) || (solverRemove.size() != 0) || (solverUpgrade.size() != 0)) { 1218 // Check whether we need to relax restriction to targetPlatform 1219 String requestPlatform = targetPlatform; 1220 List<String> requestPackages = new ArrayList<>(); 1221 requestPackages.addAll(solverInstall); 1222 requestPackages.addAll(solverRemove); 1223 requestPackages.addAll(solverUpgrade); 1224 if (ignoreMissing) { 1225 // Remove unknown packages from the list 1226 Map<String, List<DownloadablePackage>> knownNames = getPackageManager().getAllPackagesByName(); 1227 List<String> solverInstallCopy = new ArrayList<>(solverInstall); 1228 for (String pkgToInstall : solverInstallCopy) { 1229 if (!knownNames.containsKey(pkgToInstall)) { 1230 log.warn("Unable to install unknown package: " + pkgToInstall); 1231 solverInstall.remove(pkgToInstall); 1232 requestPackages.remove(pkgToInstall); 1233 } 1234 } 1235 } 1236 List<String> nonCompliantPkg = getPackageManager().getNonCompliantList(requestPackages, targetPlatform); 1237 if (nonCompliantPkg.size() > 0) { 1238 requestPlatform = null; 1239 if ("ask".equalsIgnoreCase(relax)) { 1240 relax = readConsole("Package %s not available on platform version %s.\n" 1241 + "Do you want to relax the constraint (yes/no)? [no] ", "no", 1242 StringUtils.join(nonCompliantPkg, ", "), targetPlatform); 1243 } 1244 1245 if (Boolean.parseBoolean(relax)) { 1246 log.warn(String.format("Relax restriction to target platform %s because of package(s) %s", 1247 targetPlatform, StringUtils.join(nonCompliantPkg, ", "))); 1248 } else { 1249 if (ignoreMissing) { 1250 for (String pkgToInstall : nonCompliantPkg) { 1251 log.warn("Unable to install package: " + pkgToInstall); 1252 solverInstall.remove(pkgToInstall); 1253 } 1254 } else { 1255 throw new PackageException(String.format( 1256 "Package %s not available on platform version %s (relax is not allowed)", 1257 StringUtils.join(nonCompliantPkg, ", "), targetPlatform)); 1258 } 1259 } 1260 } 1261 1262 log.debug("solverInstall: " + solverInstall); 1263 log.debug("solverRemove: " + solverRemove); 1264 log.debug("solverUpgrade: " + solverUpgrade); 1265 DependencyResolution resolution = getPackageManager().resolveDependencies(solverInstall, solverRemove, 1266 solverUpgrade, requestPlatform, allowSNAPSHOT, keepExisting); 1267 log.info(resolution); 1268 if (resolution.isFailed()) { 1269 return false; 1270 } 1271 if (resolution.isEmpty()) { 1272 pkgRemove(pkgsToRemove); 1273 return cmdOk; 1274 } 1275 if ("ask".equalsIgnoreCase(accept)) { 1276 accept = readConsole("Do you want to continue (yes/no)? [yes] ", "yes"); 1277 } 1278 if (!Boolean.parseBoolean(accept)) { 1279 log.warn("Exit"); 1280 return false; 1281 } 1282 1283 List<String> packageIdsToRemove = resolution.getOrderedPackageIdsToRemove(); 1284 List<String> packageIdsToUpgrade = resolution.getUpgradePackageIds(); 1285 List<String> packageIdsToInstall = resolution.getOrderedPackageIdsToInstall(); 1286 List<String> packagesIdsToReInstall = new ArrayList<>(); 1287 1288 // Download remote packages 1289 if (!downloadPackages(resolution.getDownloadPackageIds())) { 1290 log.error("Aborting packages change request"); 1291 return false; 1292 } 1293 1294 // Uninstall 1295 if (!packageIdsToUpgrade.isEmpty()) { 1296 // Add packages to upgrade to uninstall list 1297 // Don't use IDs to avoid downgrade instead of uninstall 1298 packageIdsToRemove.addAll(resolution.getLocalPackagesToUpgrade().keySet()); 1299 DependencyResolution uninstallResolution = getPackageManager().resolveDependencies(null, 1300 packageIdsToRemove, null, requestPlatform, allowSNAPSHOT, keepExisting); 1301 log.debug("Sub-resolution (uninstall) " + uninstallResolution); 1302 if (uninstallResolution.isFailed()) { 1303 return false; 1304 } 1305 List<String> newPackageIdsToRemove = uninstallResolution.getOrderedPackageIdsToRemove(); 1306 packagesIdsToReInstall = ListUtils.subtract(newPackageIdsToRemove, packageIdsToRemove); 1307 packagesIdsToReInstall.removeAll(packageIdsToUpgrade); 1308 packageIdsToRemove = newPackageIdsToRemove; 1309 } 1310 if (!pkgUninstall(packageIdsToRemove)) { 1311 return false; 1312 } 1313 1314 // Install 1315 if (!packagesIdsToReInstall.isEmpty()) { 1316 // Add list of packages uninstalled because of upgrade 1317 packageIdsToInstall.addAll(packagesIdsToReInstall); 1318 DependencyResolution installResolution = getPackageManager().resolveDependencies( 1319 packageIdsToInstall, null, null, requestPlatform, allowSNAPSHOT, keepExisting); 1320 log.debug("Sub-resolution (install) " + installResolution); 1321 if (installResolution.isFailed()) { 1322 return false; 1323 } 1324 packageIdsToInstall = installResolution.getOrderedPackageIdsToInstall(); 1325 } 1326 if (!pkgInstall(packageIdsToInstall)) { 1327 return false; 1328 } 1329 1330 pkgRemove(pkgsToRemove); 1331 } 1332 return cmdOk; 1333 } catch (PackageException e) { 1334 log.error(e); 1335 log.debug(e, e); 1336 return false; 1337 } 1338 } 1339 1340 /** 1341 * Installs a list of packages and uninstalls the rest (no dependency check) 1342 * 1343 * @since 5.9.2 1344 */ 1345 public boolean pkgSet(List<String> pkgList) { 1346 return pkgSet(pkgList, false); 1347 } 1348 1349 /** 1350 * Installs a list of packages and uninstalls the rest (no dependency check) 1351 * 1352 * @since 6.0 1353 */ 1354 public boolean pkgSet(List<String> pkgList, boolean ignoreMissing) { 1355 boolean cmdOK = true; 1356 cmdOK = cmdOK && pkgInstall(pkgList, ignoreMissing); 1357 List<DownloadablePackage> installedPkgs = getPackageManager().listInstalledPackages(); 1358 List<String> pkgsToUninstall = new ArrayList<>(); 1359 for (DownloadablePackage pkg : installedPkgs) { 1360 if ((!pkgList.contains(pkg.getName())) && (!pkgList.contains(pkg.getId()))) { 1361 pkgsToUninstall.add(pkg.getId()); 1362 } 1363 } 1364 if (pkgsToUninstall.size() != 0) { 1365 cmdOK = cmdOK && pkgUninstall(pkgsToUninstall); 1366 } 1367 return cmdOK; 1368 } 1369 1370 /** 1371 * Prompt user for yes/no answer 1372 * 1373 * @param message The message to display 1374 * @param defaultValue The default answer if there's no console or if "Enter" key is pressed. 1375 * @param objects Parameters to use in the message (like in {@link String#format(String, Object...)}) 1376 * @return {@code "true"} if answer is in {@link #POSITIVE_ANSWERS}, else return {@code "false"} 1377 */ 1378 protected String readConsole(String message, String defaultValue, Object... objects) { 1379 String answer; 1380 Console console = System.console(); 1381 if (console == null || StringUtils.isEmpty(answer = console.readLine(message, objects))) { 1382 answer = defaultValue; 1383 } 1384 answer = answer.trim().toLowerCase(); 1385 return parseAnswer(answer); 1386 } 1387 1388 /** 1389 * @return {@code "true"} if answer is in {@link #POSITIVE_ANSWERS}, and {@code "ask"} if answer values 1390 * {@code "ask"}, else return {@code "false"} 1391 * @since 6.0 1392 */ 1393 public static String parseAnswer(String answer) { 1394 if ("ask".equalsIgnoreCase(answer)) { 1395 return "ask"; 1396 } 1397 if ("false".equalsIgnoreCase(answer)) { 1398 return "false"; 1399 } 1400 for (String positive : POSITIVE_ANSWERS) { 1401 if (positive.equalsIgnoreCase(answer)) { 1402 return "true"; 1403 } 1404 } 1405 return "false"; 1406 } 1407 1408 protected boolean pkgUpgradeByType(PackageType type) { 1409 List<DownloadablePackage> upgrades = NuxeoConnectClient.getPackageManager().listUpdatePackages(type, 1410 targetPlatform); 1411 List<String> upgradeIds = new ArrayList<>(); 1412 for (DownloadablePackage upgrade : upgrades) { 1413 upgradeIds.add(upgrade.getId()); 1414 } 1415 return pkgRequest(null, upgradeIds, null, null); 1416 1417 } 1418 1419 public boolean pkgHotfix() { 1420 return pkgUpgradeByType(PackageType.HOT_FIX); 1421 } 1422 1423 public boolean pkgUpgrade() { 1424 return pkgUpgradeByType(PackageType.ADDON); 1425 } 1426 1427 /** 1428 * Must be called after {@link #setAccept(String)} which overwrites its value. 1429 * 1430 * @param relaxValue true, false or ask; ignored if null 1431 */ 1432 public void setRelax(String relaxValue) { 1433 if (relaxValue != null) { 1434 relax = parseAnswer(relaxValue); 1435 } 1436 } 1437 1438 /** 1439 * @param acceptValue true, false or ask; if true or ask, then calls {@link #setRelax(String)} with the same value; 1440 * ignored if null 1441 */ 1442 public void setAccept(String acceptValue) { 1443 if (acceptValue != null) { 1444 accept = parseAnswer(acceptValue); 1445 if ("ask".equals(accept) || "true".equals(accept)) { 1446 setRelax(acceptValue); 1447 } 1448 } 1449 } 1450 1451 /* 1452 * Helper for adding a new PackageInfo initialized with informations gathered from the given package. It is not put 1453 * into CommandInfo to avoid adding a dependency on Connect Client 1454 */ 1455 private PackageInfo newPackageInfo(CommandInfo cmdInfo, Package pkg) { 1456 PackageInfo packageInfo = new PackageInfo(pkg); 1457 cmdInfo.packages.add(packageInfo); 1458 return packageInfo; 1459 } 1460 1461 /** 1462 * @param packages List of packages identified by their ID, name or local filename. 1463 * @since 5.7 1464 */ 1465 public boolean pkgShow(List<String> packages) { 1466 boolean cmdOk = true; 1467 if (packages == null || packages.isEmpty()) { 1468 return cmdOk; 1469 } 1470 StringBuilder sb = new StringBuilder(); 1471 sb.append("****************************************"); 1472 for (String pkg : packages) { 1473 CommandInfo cmdInfo = cset.newCommandInfo(CommandInfo.CMD_SHOW); 1474 cmdInfo.param = pkg; 1475 try { 1476 PackageInfo packageInfo = newPackageInfo(cmdInfo, findPackage(pkg)); 1477 sb.append("\nPackage: " + packageInfo.id); 1478 sb.append("\nState: " + packageInfo.state); 1479 sb.append("\nVersion: " + packageInfo.version); 1480 sb.append("\nName: " + packageInfo.name); 1481 sb.append("\nType: " + packageInfo.type); 1482 sb.append("\nVisibility: " + packageInfo.visibility); 1483 if (packageInfo.state == PackageState.REMOTE && packageInfo.type != PackageType.STUDIO 1484 && packageInfo.visibility != PackageVisibility.PUBLIC 1485 && !LogicalInstanceIdentifier.isRegistered()) { 1486 sb.append(" (registration required)"); 1487 } 1488 sb.append("\nTarget platforms: " + ArrayUtils.toString(packageInfo.targetPlatforms)); 1489 appendIfNotEmpty(sb, "\nVendor: ", packageInfo.vendor); 1490 sb.append("\nSupports hot-reload: " + packageInfo.supportsHotReload); 1491 sb.append("\nSupported: " + packageInfo.supported); 1492 sb.append("\nProduction state: " + packageInfo.productionState); 1493 sb.append("\nValidation state: " + packageInfo.validationState); 1494 appendIfNotEmpty(sb, "\nProvides: ", packageInfo.provides); 1495 appendIfNotEmpty(sb, "\nDepends: ", packageInfo.dependencies); 1496 appendIfNotEmpty(sb, "\nConflicts: ", packageInfo.conflicts); 1497 appendIfNotEmpty(sb, "\nTitle: ", packageInfo.title); 1498 appendIfNotEmpty(sb, "\nDescription: ", packageInfo.description); 1499 appendIfNotEmpty(sb, "\nHomepage: ", packageInfo.homePage); 1500 appendIfNotEmpty(sb, "\nLicense: ", packageInfo.licenseType); 1501 appendIfNotEmpty(sb, "\nLicense URL: ", packageInfo.licenseUrl); 1502 sb.append("\n****************************************"); 1503 } catch (PackageException e) { 1504 cmdOk = false; 1505 cmdInfo.exitCode = 1; 1506 cmdInfo.newMessage(e); 1507 } 1508 } 1509 log.info(sb.toString()); 1510 return cmdOk; 1511 } 1512 1513 private void appendIfNotEmpty(StringBuilder sb, String label, Object[] array) { 1514 if (ArrayUtils.isNotEmpty(array)) { 1515 sb.append(label + ArrayUtils.toString(array)); 1516 } 1517 } 1518 1519 private void appendIfNotEmpty(StringBuilder sb, String label, String value) { 1520 if (StringUtils.isNotEmpty(value)) { 1521 sb.append(label + value); 1522 } 1523 } 1524 1525 /** 1526 * Looks for a package. First look if it's a local ZIP file, second if it's a local package and finally if it's a 1527 * remote package. 1528 * 1529 * @param pkg A ZIP filename or file path, or package ID or a package name. 1530 * @return The first package found matching the given string. 1531 * @throws PackageException If no package is found or if an issue occurred while searching. 1532 * @see PackageDefinition 1533 * @see LocalPackage 1534 * @see DownloadablePackage 1535 */ 1536 protected Package findPackage(String pkg) throws PackageException { 1537 // Is it a local ZIP file? 1538 File localPackageFile = getLocalPackageFile(pkg); 1539 if (localPackageFile != null) { 1540 return service.loadPackageFromZip(localPackageFile); 1541 } 1542 1543 // Is it a local package ID or name? 1544 LocalPackage localPackage = getLocalPackage(pkg); 1545 if (localPackage != null) { 1546 return localPackage; 1547 } 1548 1549 // Is it a remote package ID or name? 1550 String pkgId = getRemotePackageId(pkg); 1551 if (pkgId != null) { 1552 return getPackageManager().findPackageById(pkgId); 1553 } 1554 1555 throw new PackageException("Could not find a remote or local (relative to " 1556 + "current directory or to NUXEO_HOME) " + "package with name or ID " + pkg); 1557 } 1558 1559 /** 1560 * @since 5.9.1 1561 */ 1562 public void setAllowSNAPSHOT(boolean allow) { 1563 CUDFHelper.defaultAllowSNAPSHOT = allow; 1564 allowSNAPSHOT = allow; 1565 } 1566 1567}