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