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