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