1 19 20 package com.sslexplorer.extensions; 21 22 import java.io.File ; 23 import java.io.IOException ; 24 import java.util.ArrayList ; 25 import java.util.Arrays ; 26 import java.util.Collection ; 27 import java.util.Iterator ; 28 29 import org.apache.commons.logging.Log; 30 import org.apache.commons.logging.LogFactory; 31 import org.jdom.Attribute; 32 import org.jdom.DataConversionException; 33 import org.jdom.Document; 34 import org.jdom.Element; 35 import org.jdom.JDOMException; 36 import org.jdom.input.SAXBuilder; 37 38 import com.sslexplorer.boot.Util; 39 import com.sslexplorer.boot.VersionInfo; 40 import com.sslexplorer.boot.VersionInfo.Version; 41 import com.sslexplorer.extensions.store.ExtensionStore; 42 import com.sslexplorer.extensions.types.Plugin; 43 import com.sslexplorer.extensions.types.PluginType; 44 45 81 public class ExtensionBundle extends ArrayList <ExtensionDescriptor> implements Comparable { 82 83 final static Log log = LogFactory.getLog(ExtensionBundle.class); 84 85 89 public static final int TYPE_UPDATEABLE = 0; 90 91 95 public static final int TYPE_INSTALLED = 1; 96 97 101 public static final int TYPE_INSTALLABLE = 2; 102 103 109 public static final int TYPE_CONFIGUREABLE = 3; 110 111 116 public static final int TYPE_PENDING_REMOVAL = 4; 117 118 123 public static final int TYPE_PENDING_INSTALLATION = 5; 124 125 130 public static final int TYPE_PENDING_UPDATE = 6; 131 132 138 public static final int TYPE_PENDING_STATE_CHANGE = 7; 139 140 143 public enum ExtensionBundleStatus { 144 147 ENABLED(0, "enabled"), 148 149 152 DISABLED(1, "disabled"), 153 154 157 SYSTEM_DISABLED(2, "systemDisabled"), 158 159 162 STARTED(3, "started"), 163 164 167 ACTIVATED(4, "activated"), 168 169 172 ERROR(6, "error"); 173 174 private String name; 175 176 private ExtensionBundleStatus(int state, String name) { 177 this.name = name; 178 } 179 180 185 public boolean isStartedOrActivated() { 186 return this == ACTIVATED || this == STARTED; 187 } 188 189 194 public String getName() { 195 return name; 196 } 197 198 201 public boolean isDisabled() { 202 return this == DISABLED || this == SYSTEM_DISABLED; 203 } 204 } 205 206 208 private File descriptor; 209 private Document doc; 210 private String description; 211 private String license; 212 private String productURL; 213 private String instructionsURL; 214 private VersionInfo.Version version; 215 private int type; 216 private int order; 217 private String id; 218 private String name; 219 private String licenseFilePath; 220 private VersionInfo.Version requiredHostVersion; 221 private ExtensionInstaller installer; 222 private String category; 223 private boolean mandatoryUpdate; 224 private ExtensionBundleStatus status = ExtensionBundleStatus.ENABLED; 225 private Collection <String > dependencyNames; 226 private Throwable error; 227 private boolean hidden; 228 private boolean devExtension; 229 private Element messageElement; 230 private String changes; 231 private VersionInfo.Version updateVersion; 232 233 253 public ExtensionBundle(Version version, int type, String id, String name, String description, String license, 254 String productURL, String instructionsURL, VersionInfo.Version requiredHostVersion, 255 Collection <String > dependencyNames, String category, boolean mandatoryUpdate, int order, 256 String changes) { 257 this.version = version; 258 this.type = type; 259 this.id = id; 260 this.name = name; 261 this.description = description; 262 this.license = license; 263 this.productURL = productURL; 264 this.order = order; 265 this.instructionsURL = instructionsURL; 266 this.requiredHostVersion = requiredHostVersion; 267 this.dependencyNames = dependencyNames; 268 this.category = category; 269 this.mandatoryUpdate = mandatoryUpdate; 270 this.changes = changes; 271 } 272 273 280 public ExtensionBundle(File descriptor, boolean devExtension) { 281 this.descriptor = descriptor; 282 this.devExtension = devExtension; 283 } 284 285 290 public boolean isDevExtension() { 291 return devExtension; 292 } 293 294 299 public boolean isHidden() { 300 return hidden; 301 } 302 303 308 public String getId() { 309 return id; 310 } 311 312 316 public boolean isUpdateable() { 317 return getType() == ExtensionBundle.TYPE_UPDATEABLE; 318 } 319 320 327 public int getOrder() { 328 return order; 329 } 330 331 336 public String getName() { 337 return name; 338 } 339 340 347 public File getFile() { 348 return descriptor; 349 } 350 351 357 public String getCategory() { 358 return category; 359 } 360 361 369 public Collection <String > getDependencies() { 370 return dependencyNames; 371 } 372 373 380 public synchronized void start() throws ExtensionException { 381 382 try { 383 if (log.isInfoEnabled()) { 384 log.info("Starting extension bundle " + getId()); 385 } 386 387 if (getStatus() != ExtensionBundleStatus.ENABLED) { 389 throw new ExtensionException(ExtensionException.INVALID_EXTENSION_BUNDLE_STATUS, 390 getId(), 391 "Bundle is not in enabled state."); 392 } 393 394 checkDependenciesStarted(this); 396 397 ExtensionException ee = null; 399 for (Iterator i = iterator(); i.hasNext();) { 400 ExtensionDescriptor d = (ExtensionDescriptor) i.next(); 401 try { 402 d.start(); 403 404 setBundleMessages(d); 406 407 status = ExtensionBundleStatus.STARTED; 408 } catch (ExtensionException ex) { 409 if (ee == null) { 410 ee = ex; 411 } 412 }catch (Throwable t){ 413 if (ee == null) { 414 ee = new ExtensionException(ExtensionException.INTERNAL_ERROR, t); 415 } 416 } 417 } 418 if (ee != null) { 419 throw ee; 420 } 421 } catch (ExtensionException ee) { 422 log.error("Failed to start extension. ", ee); 423 error = ee; 424 status = ExtensionBundleStatus.ERROR; 425 throw ee; 426 } 427 428 error = null; 429 } 430 431 private void setBundleMessages(ExtensionDescriptor d) throws ExtensionException { 432 if(messageElement != null) { 433 for (Iterator i2 = messageElement.getChildren().iterator(); i2.hasNext();) { 434 Element el = (Element) i2.next(); 435 if (!el.getName().equals("message")) { 436 throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, 437 "<messages> element may only contain <message> elements."); 438 } 439 String key = el.getAttributeValue("key"); 440 if (key == null) { 441 throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, 442 "<message> element must have a key attribute."); 443 } 444 String aKey = "application." + d.getId() + "." + key; 445 if (d.getMessageResources() != null) { 446 if (!d.getMessageResources().isPresent(key)) { 447 d.getMessageResources().setMessage(el.getAttributeValue("locale"), aKey, el.getText()); 448 } 449 } 450 } 451 } 452 } 453 454 void checkDependenciesStarted(ExtensionBundle bundle) throws ExtensionException { 455 if (bundle.getDependencies() != null) { 456 for (String dep : bundle.getDependencies()) { 457 if (!ExtensionStore.getInstance().isExtensionBundleLoaded(dep)) { 458 throw new ExtensionException(ExtensionException.DEPENDENCY_NOT_INSTALLED, dep, getId()); 459 } 460 ExtensionBundle depBundle = ExtensionStore.getInstance().getExtensionBundle(dep); 461 if (!depBundle.getStatus().isStartedOrActivated()) { 462 throw new ExtensionException(ExtensionException.DEPENDENCY_NOT_STARTED, dep, getId()); 463 } 464 checkDependenciesStarted(depBundle); 465 } 466 } 467 468 } 469 470 void checkDependenciesActivated(ExtensionBundle bundle) throws ExtensionException { 471 if (bundle.getDependencies() != null) { 472 for (String dep : bundle.getDependencies()) { 473 if (!ExtensionStore.getInstance().isExtensionBundleLoaded(dep)) { 474 throw new ExtensionException(ExtensionException.DEPENDENCY_NOT_INSTALLED, dep, getId()); 475 } 476 ExtensionBundle depBundle = ExtensionStore.getInstance().getExtensionBundle(dep); 477 if (depBundle.getStatus() != ExtensionBundleStatus.ACTIVATED) { 478 throw new ExtensionException(ExtensionException.DEPENDENCY_NOT_STARTED, dep, getId()); 479 } 480 checkDependenciesActivated(depBundle); 481 } 482 } 483 484 } 485 486 493 public synchronized void activate() throws ExtensionException { 494 try { 495 if (getStatus() != ExtensionBundleStatus.STARTED) { 496 throw new ExtensionException(ExtensionException.INVALID_EXTENSION_BUNDLE_STATUS, 497 getId(), 498 "Bundle is not in started so cannot be activated."); 499 } 500 501 checkDependenciesActivated(this); 503 504 ExtensionException ee = null; 505 for (Iterator i = iterator(); i.hasNext();) { 506 ExtensionDescriptor d = (ExtensionDescriptor) i.next(); 507 try { 508 d.activate(); 509 status = ExtensionBundleStatus.ACTIVATED; 510 } catch (ExtensionException ex) { 511 if (ee == null) { 512 ee = ex; 513 } 514 }catch (Throwable t){ 515 if (ee == null) { 516 ee = new ExtensionException(ExtensionException.INTERNAL_ERROR, t); 517 } 518 } 519 } 520 if (ee != null) { 521 throw ee; 522 } 523 } catch (ExtensionException ee) { 524 log.error("Failed to activate extension bundle. ", ee); 525 error = ee; 526 status = ExtensionBundleStatus.ERROR; 527 throw ee; 528 } 529 error = null; 530 } 531 532 539 public synchronized void stop() throws ExtensionException { 540 ExtensionException ee = null; 541 try { 542 for (Iterator i = iterator(); i.hasNext();) { 543 ExtensionDescriptor d = (ExtensionDescriptor) i.next(); 544 try { 545 d.stop(); 546 } catch (ExtensionException ex) { 547 if (ee == null) { 548 ee = ex; 549 } 550 log.error("Failed to stop extension bundle. ", ex); 551 } 552 } 553 if (ee != null) { 554 throw ee; 555 } 556 } finally { 557 status = ExtensionBundleStatus.ENABLED; 558 } 559 } 560 561 568 public synchronized void load() throws ExtensionException { 569 570 try { 571 if (log.isInfoEnabled()) { 572 log.info("Loading bundle from " + getFile().getAbsolutePath()); 573 } 574 575 installer = new ExtensionInstaller(this); 576 SAXBuilder sax = new SAXBuilder(); 577 try { 578 doc = sax.build(descriptor); 579 } catch (JDOMException jde) { 580 jde.printStackTrace(); 581 throw new ExtensionException(ExtensionException.FAILED_TO_PARSE_DESCRIPTOR, jde); 582 } catch (IOException ioe) { 583 throw new ExtensionException(ExtensionException.INTERNAL_ERROR, ioe, "Failed to load descriptor for parsing."); 584 } 585 586 hidden = "true".equals(doc.getRootElement().getAttributeValue("hidden")); 587 license = doc.getRootElement().getAttributeValue("license"); 588 license = license == null || license.equals("") ? "Unknown" : license; 589 if (log.isDebugEnabled()) 590 log.debug("Application bundle license is " + license); 591 592 productURL = doc.getRootElement().getAttributeValue("productURL"); 593 instructionsURL = doc.getRootElement().getAttributeValue("instructionsURL"); 594 595 dependencyNames = null; 597 String dependencies = doc.getRootElement().getAttributeValue("dependencies"); 598 if (dependencies != null) { 599 log.warn("DEPRECATED. dependencies attribute in bundle " + getFile().getAbsolutePath() 600 + " should now use 'depends'."); 601 } else { 602 dependencies = doc.getRootElement().getAttributeValue("depends"); 603 } 604 if (!Util.isNullOrTrimmedBlank(dependencies)) { 605 dependencyNames = Arrays.asList(dependencies.split(",")); 606 } 607 608 String requiredHostVersionString = doc.getRootElement().getAttributeValue("requiredHostVersion"); 610 if (requiredHostVersionString != null && !"any".equalsIgnoreCase(requiredHostVersionString)) { 611 requiredHostVersion = new VersionInfo.Version(requiredHostVersionString); 612 int dif = requiredHostVersion.compareTo(VersionInfo.getVersion()); 613 if (dif > 0) 614 throw new ExtensionException(ExtensionException.INSUFFICIENT_SSLEXPLORER_HOST_VERSION, 615 getName(), 616 requiredHostVersionString); 617 618 } else { 619 requiredHostVersion = null; 620 } 621 622 String ver = doc.getRootElement().getAttributeValue("version"); 623 if (ver == null || ver.equals("")) { 624 throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, 625 "<applications> element requires the attribute 'version'."); 626 } 627 version = new VersionInfo.Version(ver); 628 629 if (doc.getRootElement().getName().equals("bundle")) { 630 631 Attribute a = doc.getRootElement().getAttribute("id"); 632 id = a == null ? null : a.getValue(); 633 634 if (id == null) { 635 throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, 636 "<bundle> element requires attribute 'id'"); 637 } 638 if (log.isDebugEnabled()) 639 log.debug("Application bundle id is " + id); 640 641 name = doc.getRootElement().getAttribute("name").getValue(); 642 if (log.isDebugEnabled()) 643 log.debug("Application bundle name is " + name); 644 645 Attribute orderAttr = doc.getRootElement().getAttribute("order"); 646 if (orderAttr == null) { 647 log.warn("<bundle> element in " + getFile().getPath() + " now requires attribute 'order'. Assuming 99999"); 648 order = 99999; 649 } else { 650 try { 651 order = orderAttr.getIntValue(); 652 } catch (DataConversionException dce) { 653 throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, 654 "'order' attribute is invalid. " + dce.getMessage()); 655 } 656 } 657 658 licenseFilePath = doc.getRootElement().getAttributeValue("licenseAgreement"); 659 660 if (name == null) { 661 throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, 662 "<bundle> element requires the attribute 'name'"); 663 } 664 665 666 667 for (Iterator i = doc.getRootElement().getChildren().iterator(); i.hasNext();) { 668 Element e = (Element) i.next(); 669 if (e.getName().equalsIgnoreCase("description")) { 670 description = Util.trimmedBothOrBlank(e.getText()); 671 } else if (e.getName().equalsIgnoreCase("install")) { 672 processInstall(e); 673 } else if (e.getName().equalsIgnoreCase("messages")) { 674 messageElement = e; 676 } else if (e.getName().equals("application") || e.getName().equals("extension")) { 677 ExtensionDescriptor desc = new ExtensionDescriptor(); 678 desc.load(this, e); 679 add(desc); 680 } else { 681 throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, 682 "<bundle> element may only contain <description> or <extension> (or the deprecated <application>) elements."); 683 } 684 } 685 686 } else if (doc.getRootElement().getName().equals("application") || doc.getRootElement().getName().equals("extension")) { 687 log.warn("DEPRECATED. All extensions should now use the <bundle> tag, " + getFile().getPath() 688 + " is using not using this tag."); 689 ExtensionDescriptor desc = new ExtensionDescriptor(); 690 desc.load(this, doc.getRootElement()); 691 id = desc.getId(); 692 name = desc.getName(); 693 description = desc.getDescription(); 694 order = 99999; 695 dependencyNames = Arrays.asList(new String [] { "sslexplorer-community-applications", 696 "sslexplorer-community-tunnels" }); 697 add(desc); 698 } else { 699 throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, 700 "Application bundle root element must be <bundle> (or the deprecated <application> or <extension>) elements."); 701 } 702 703 setType(TYPE_INSTALLED); 707 } catch (ExtensionException ee) { 708 error = ee; 709 throw ee; 710 } 711 712 error = null; 713 714 } 715 716 721 public boolean isContainsPlugin() { 722 boolean containsPlugin = false; 723 for (ExtensionDescriptor descriptor : this) { 724 if (descriptor.getTypeName().equals(PluginType.TYPE)) { 725 containsPlugin = true; 726 } 727 } 728 return containsPlugin; 729 } 730 731 738 public void removeBundle() throws Exception { 739 if (log.isInfoEnabled()) 740 log.info("Removing extension bundle " + getId()); 741 boolean containsPlugin = isContainsPlugin(); 742 try { 743 if (!containsPlugin && getStatus().isStartedOrActivated()) { 745 stop(); 746 } 747 748 for (ExtensionDescriptor descriptor : this) { 750 descriptor.removeDescriptor(); 751 } 752 } finally { 753 if (containsPlugin) { 754 setType(ExtensionBundle.TYPE_PENDING_REMOVAL); 755 } 756 } 757 } 758 759 765 public ExtensionInstaller getInstaller() { 766 return installer; 767 } 768 769 public String getInstructionsURL() { 770 return instructionsURL; 771 } 772 773 public String getProductURL() { 774 return productURL; 775 } 776 777 public VersionInfo.Version getVersion() { 778 return version; 779 } 780 781 public VersionInfo.Version getDisplayVersion() { 782 return isUpdateable() ? updateVersion : version; 783 } 784 785 public String getDescription() { 786 return description; 787 } 788 789 public void setDescription(String description) { 790 this.description = description; 791 } 792 793 public void setLicense(String license) { 794 this.license = license; 795 } 796 797 public String getLicense() { 798 return license; 799 } 800 801 public int getType() { 802 return type; 803 } 804 805 public void setType(int type) { 806 this.type = type; 807 } 808 809 public File getBaseDir() { 810 return getFile() == null ? null : getFile().getParentFile(); 811 } 812 813 817 public boolean containsApplication(String application) { 818 for (Iterator i = iterator(); i.hasNext();) { 819 if (((ExtensionDescriptor) i.next()).getId().equals(application)) { 820 return true; 821 } 822 } 823 return false; 824 } 825 826 830 public ExtensionDescriptor getApplicationDescriptor(String id) { 831 for (Iterator i = iterator(); i.hasNext();) { 832 ExtensionDescriptor app = (ExtensionDescriptor) i.next(); 833 if (app.getId().equals(id)) { 834 return app; 835 } 836 } 837 return null; 838 } 839 840 public int compareTo(Object arg0) { 841 int c = getType() - ((ExtensionBundle) arg0).getType(); 842 return c != 0 ? c : name.compareTo(((ExtensionBundle) arg0).name); 843 } 844 845 public File getLicenseFile() { 846 File baseDir = getBaseDir(); 847 return baseDir == null || licenseFilePath == null ? null : new File (baseDir, licenseFilePath); 848 } 849 850 public VersionInfo.Version getRequiredHostVersion() { 851 return requiredHostVersion; 852 } 853 854 public void setRequiredHostVersion(VersionInfo.Version requiredHostVersion) { 855 this.requiredHostVersion = requiredHostVersion; 856 } 857 858 public void setCategory(String category) { 859 this.category = category; 860 } 861 862 public boolean isMandatoryUpdate() { 863 return mandatoryUpdate; 864 } 865 866 public Throwable getError() { 867 return error; 868 } 869 870 public boolean canStop() { 871 boolean canStop = status.isStartedOrActivated(); 872 if (!canStop) { 873 return false; 874 } 875 for (Iterator i = iterator(); i.hasNext();) { 876 ExtensionDescriptor d = (ExtensionDescriptor) i.next(); 877 if (!d.canStop()) { 878 return false; 879 } 880 } 881 return true; 882 883 } 884 885 public boolean canStart() { 886 return ExtensionBundleStatus.ENABLED.equals(status) && getType() != ExtensionBundle.TYPE_PENDING_INSTALLATION 887 && getType() != ExtensionBundle.TYPE_PENDING_REMOVAL 888 && getType() != ExtensionBundle.TYPE_PENDING_UPDATE 889 && getType() != ExtensionBundle.TYPE_PENDING_STATE_CHANGE; 890 } 891 892 public boolean canDisable() { 893 return !ExtensionBundleStatus.DISABLED.equals(status) && type != TYPE_CONFIGUREABLE && type != TYPE_INSTALLABLE && type != TYPE_PENDING_STATE_CHANGE; 894 } 895 896 public boolean canEnable() { 897 return ExtensionBundleStatus.DISABLED.equals(status) && type != TYPE_PENDING_STATE_CHANGE; 898 } 899 900 public ExtensionBundleStatus getStatus() { 901 return status; 902 } 903 904 public void setStatus(ExtensionBundleStatus status) { 905 this.status = status; 906 } 907 908 private void processInstall(Element installElement) throws ExtensionException { 909 String when = installElement.getAttributeValue("when"); 910 when = when == null ? ExtensionInstaller.ON_ACTIVATE : when; 911 for (Iterator i = installElement.getChildren().iterator(); i.hasNext();) { 912 Element e = (Element) i.next(); 913 if (e.getName().equalsIgnoreCase("mkdir")) { 914 String dir = Util.trimmedBothOrBlank(e.getText()); 915 if (dir == null || dir.equals("")) { 916 throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, 917 "<mkdir> contents must be the name of a directory to create."); 918 } 919 installer.addOp(new ExtensionInstaller.MkdirInstallOp(when, dir)); 920 } else if (e.getName().equalsIgnoreCase("cp")) { 921 String from = Util.trimmedBothOrBlank(e.getText()); 922 String to = e.getAttributeValue("to"); 923 String toDir = e.getAttributeValue("toDir"); 924 boolean overwrite = "true".equalsIgnoreCase(e.getAttributeValue("overwrite")); 925 if (from == null || from.equals("") || ((to == null || to.equals("")) && (toDir == null || toDir.equals("")))) { 926 throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, 927 "<cp> content must be the source path and the tag must have either a to or toDir attribute."); 928 } 929 installer.addOp(new ExtensionInstaller.CpInstallOp(when, from, to, toDir, overwrite)); 930 } else if (e.getName().equalsIgnoreCase("rm")) { 931 String path = Util.trimmedBothOrBlank(e.getText()); 932 if (path == null || path.equals("")) { 933 throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, 934 "<rm> must a path as its content."); 935 } 936 installer.addOp(new ExtensionInstaller.RmInstallOp(when, path)); 937 } else if (e.getName().equalsIgnoreCase("custom")) { 938 String clazz = Util.trimmedBothOrBlank(e.getText()); 939 if (clazz == null || clazz.equals("")) { 940 throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, 941 "<custom> must provide a class name that implements ExtensionInstaller.ExtensionInstallOp as its content."); 942 } 943 try { 944 installer.addOp(new ExtensionInstaller.CustomInstallOpWrapper(when, clazz)); 945 } catch (Exception ex) { 946 throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, 947 ex, "Failed to create <custom> install op."); 948 949 } 950 } 951 } 952 } 953 954 public String getChanges() { 955 return changes==null ? "" : changes.trim(); 956 } 957 958 public String toString() { 959 return id + " " + version; 960 } 961 962 public VersionInfo.Version getUpdateVersion() { 963 return updateVersion; 964 } 965 966 public void setUpdateVersion(VersionInfo.Version updateVersion) { 967 this.updateVersion = updateVersion; 968 } 969 970 public void setChanges(String changes) { 971 this.changes = changes; 972 } 973 } | Popular Tags |