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