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