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