Java tutorial
/* * SSL-Explorer * * Copyright (C) 2003-2006 3SP LTD. All Rights Reserved * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package com.sslexplorer.extensions; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jdom.Attribute; import org.jdom.DataConversionException; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; import com.sslexplorer.boot.Util; import com.sslexplorer.boot.VersionInfo; import com.sslexplorer.boot.VersionInfo.Version; import com.sslexplorer.extensions.store.ExtensionStore; import com.sslexplorer.extensions.types.Plugin; import com.sslexplorer.extensions.types.PluginType; /** * Extension bundles describe a collection of <i>Extension Descriptors</i>. * <p> * A bundle may come from one of two places, either the <i>3SP Application Store</i> * or from an installed extension bundle retrieved via the <i>Repository</i>. * <p> * If from the 3SP Application Store, the descriptor will not contain all * details, only enough to determine the name, description, versions, * dependencies and a few others pieces of information. Such bundles do not have * a life cycle in the way local extension bundles do (i.e. be <i>Started</i>, * <i>Stopped</i> or <i>Activated</i>). * <p> * Local extension bundles must go through 3 or 4 phases. * <ul> * <li>Phase 1. Loading. The XML extension descriptors are loaded and parsed. * No change the state of SSL-Explorer is made at this stage. Once this is * complete we know the order the extensions should be started</li> * <li>Phase 2. Starting. All extensions now have their * {@link ExtensionDescriptor#start()} method called. This in turn delegates the * start to the <i>Extension Type</i> implementation in use. For example, the * {@link PluginType} would create the plug-in instance and invoke the * {@link Plugin#startPlugin(com.sslexplorer.extensions.types.PluginDefinition, ExtensionDescriptor, Element)}. * If starting fails, an exception is thrown and the extension will be * <i>Stopped</i>. method on it.</li> * <li>Phase 3. Activation. Only the <i>Plug-ins</i> really use this as they * require two phases of initialisation. If activate fails, an exception is * thrown and the extension will be <i>Stopped</i>. </li> * <li>Phase 4. Stopping. May occur either after <i>Starting</i> or * <i>Activation</i>. During this phase the extension should clean up as much * as possible (deregister property definitions, user databases or any other * extension point). * </ul> */ public class ExtensionBundle extends ArrayList<ExtensionDescriptor> implements Comparable { final static Log log = LogFactory.getLog(ExtensionBundle.class); /** * Extension 'type' code indicating a new version is available from the 3SP * Application Store */ public static final int TYPE_UPDATEABLE = 0; /** * Extension 'type' code indicating the most up to date version in 3SP * Application Store is already correctly installed */ public static final int TYPE_INSTALLED = 1; /** * Extension 'type' code indicating the bundle is not currently installed * but available from the 3SP Application Store */ public static final int TYPE_INSTALLABLE = 2; /** * Extension 'type' code indicating the bundle is not an installable bundle, * merely a pointer to further instructions as to how to create a complete * bundle (this is for bundles that we cannot legally distribute all * components) */ public static final int TYPE_CONFIGUREABLE = 3; /** * Extension 'type' code indicating the bundle has been removed by the * administrator but cannot yet be deleted from the local file system as it * contains plug-ins that are in use. */ public static final int TYPE_PENDING_REMOVAL = 4; /** * Extension 'type' code indicating a new bundle has been installed but * cannot be started because it contains plug-ins. The administrator should * restart the entire server to complete the installation. */ public static final int TYPE_PENDING_INSTALLATION = 5; /** * Extension 'type' code indicating a bundle has been updated but cannot be * restarted because it contains plug-ins. The administrator should restart * the entire server to complete the update. */ public static final int TYPE_PENDING_UPDATE = 6; /** * Extension 'type' code indicating a bundle state has been changed * (i.e. enable or disable) but cannot be restarted because it contains * plug-ins. The administrator should restart the entire server to complete * the change. */ public static final int TYPE_PENDING_STATE_CHANGE = 7; /** * Status of extension bundle */ public enum ExtensionBundleStatus { /** * The extension is enabled, but stopped */ ENABLED(0, "enabled"), /** * The extension is disabled */ DISABLED(1, "disabled"), /** * The extension has been disabled by the system (cannot be enabled */ SYSTEM_DISABLED(2, "systemDisabled"), /** * The extension is enabled and started */ STARTED(3, "started"), /** * The extension is enabled and activated */ ACTIVATED(4, "activated"), /** * The extension is errored */ ERROR(6, "error"); private String name; private ExtensionBundleStatus(int state, String name) { this.name = name; } /** * Determine if the state is {@link #STARTED} or {@link #ACTIVATED}. * * @return started or activated */ public boolean isStartedOrActivated() { return this == ACTIVATED || this == STARTED; } /** * Get the status name * * @return name */ public String getName() { return name; } /** * @return boolean */ public boolean isDisabled() { return this == DISABLED || this == SYSTEM_DISABLED; } } // Private instance variables private File descriptor; private Document doc; private String description; private String license; private String productURL; private String instructionsURL; private VersionInfo.Version version; private int type; private int order; private String id; private String name; private boolean startOnSetupMode; private String licenseFilePath; private VersionInfo.Version requiredHostVersion; private ExtensionInstaller installer; private String category; private boolean mandatoryUpdate; private ExtensionBundleStatus status = ExtensionBundleStatus.ENABLED; private Collection<String> dependencyNames; private Throwable error; private boolean hidden; private boolean devExtension; private Element messageElement; private String changes; private VersionInfo.Version updateVersion; private String platform = ""; private String arch = ""; /** * Constructor for when creating a bundle without having a XML extension * descriptor stream. * * @param version * @param type * @param id * @param name * @param description * @param license * @param productURL * @param instructionsURL * @param requiredHostVersion * @param dependencyNames collection of dependency names or * <code>null</code> for no dependencies * @param category * @param mandatoryUpdate * @param order * @param changes * @param password * @param arch */ public ExtensionBundle(Version version, int type, String id, String name, String description, String license, String productURL, String instructionsURL, VersionInfo.Version requiredHostVersion, Collection<String> dependencyNames, String category, boolean mandatoryUpdate, int order, String changes, String platform, String arch) { this.version = version; this.type = type; this.id = id; this.name = name; this.description = description; this.license = license; this.productURL = productURL; this.order = order; this.instructionsURL = instructionsURL; this.requiredHostVersion = requiredHostVersion; this.dependencyNames = dependencyNames; this.category = category; this.mandatoryUpdate = mandatoryUpdate; this.changes = changes; this.platform = platform; this.arch = arch; } /** * Constructor for creating a new bundle given a file that contains an XML * extension bundle descriptor. * * @param descriptor descriptor * @param devExtension loaded as dev extension */ public ExtensionBundle(File descriptor, boolean devExtension) { this.descriptor = descriptor; this.devExtension = devExtension; } /** * Get if this bundle was loaded as a <i>devExtension</i>. * * @return dev extension */ public boolean isDevExtension() { return devExtension; } /** * Get if this bundle is hidden * * @return hidden */ public boolean isHidden() { return hidden; } /** * Get the extension bundle ID. * * @return bundle ID */ public String getId() { return id; } /** * Determines if the extension bundle is updateable. * @return boolean */ public boolean isUpdateable() { return getType() == ExtensionBundle.TYPE_UPDATEABLE; } /** * Get the order. This determines which order the bundle will get loaded * started and activated in (in relative to other extensions). The lower the * number the earlier the extension should be loaded * * @return order */ public int getOrder() { return order; } /** * Get the english name of this extension bundle. * * @return english name of bundle */ public String getName() { return name; } /** * Get the extension descriptor file that represents this bundle. If the * bundle is not loaded locally (i.e. it came from the 3SP Application * Store) then this will be <code>null</code>. * * @return extension descriptor file */ public File getFile() { return descriptor; } /** * Get the category for this bundle. This is used in the extension manager * front end to group available extensions. * * @return category */ public String getCategory() { return category; } /** * Get a collection of the names of the extensions bundles this bundle * depends on. If any of the dependencies are not satisfied the extension * may not be started. If there are no dependencies <code>null</code> will * be returned. * * @return extension bundle dependency names */ public Collection<String> getDependencies() { return dependencyNames; } /** * Start all extension bundles. This method will start all extension * descriptors it contains and is the second phase in an extensions life * cycle (after loading). * * @throws ExtensionException if any bundle could not be started */ public synchronized void start() throws ExtensionException { try { if (log.isInfoEnabled()) { log.info("Starting extension bundle " + getId()); } // Check we are allowed to start this bundle if (getStatus() != ExtensionBundleStatus.ENABLED) { throw new ExtensionException(ExtensionException.INVALID_EXTENSION_BUNDLE_STATUS, getId(), "Bundle is not in enabled state."); } // Check this bundles dependencies are installed and started checkDependenciesStarted(this); // Start all extensions in this bundle ExtensionException ee = null; for (Iterator i = iterator(); i.hasNext();) { ExtensionDescriptor d = (ExtensionDescriptor) i.next(); try { d.start(); // Set any bundle messages setBundleMessages(d); status = ExtensionBundleStatus.STARTED; } catch (ExtensionException ex) { if (ee == null) { ee = ex; } } catch (Throwable t) { if (ee == null) { ee = new ExtensionException(ExtensionException.INTERNAL_ERROR, t); } } } if (ee != null) { throw ee; } } catch (ExtensionException ee) { log.error("Failed to start extension. ", ee); error = ee; status = ExtensionBundleStatus.ERROR; throw ee; } error = null; } private void setBundleMessages(ExtensionDescriptor d) throws ExtensionException { if (messageElement != null) { for (Iterator i2 = messageElement.getChildren().iterator(); i2.hasNext();) { Element el = (Element) i2.next(); if (!el.getName().equals("message")) { throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, "<messages> element may only contain <message> elements."); } String key = el.getAttributeValue("key"); if (key == null) { throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, "<message> element must have a key attribute."); } String aKey = "application." + d.getId() + "." + key; if (d.getMessageResources() != null) { if (!d.getMessageResources().isPresent(key)) { d.getMessageResources().setMessage(el.getAttributeValue("locale"), aKey, el.getText()); } } } } } void checkDependenciesStarted(ExtensionBundle bundle) throws ExtensionException { if (bundle.getDependencies() != null) { for (String dep : bundle.getDependencies()) { if (!ExtensionStore.getInstance().isExtensionBundleLoaded(dep)) { throw new ExtensionException(ExtensionException.DEPENDENCY_NOT_INSTALLED, dep, getId()); } ExtensionBundle depBundle = ExtensionStore.getInstance().getExtensionBundle(dep); if (!depBundle.getStatus().isStartedOrActivated()) { throw new ExtensionException(ExtensionException.DEPENDENCY_NOT_STARTED, dep, getId()); } checkDependenciesStarted(depBundle); } } } void checkDependenciesActivated(ExtensionBundle bundle) throws ExtensionException { if (bundle.getDependencies() != null) { for (String dep : bundle.getDependencies()) { if (!ExtensionStore.getInstance().isExtensionBundleLoaded(dep)) { throw new ExtensionException(ExtensionException.DEPENDENCY_NOT_INSTALLED, dep, getId()); } ExtensionBundle depBundle = ExtensionStore.getInstance().getExtensionBundle(dep); if (depBundle.getStatus() != ExtensionBundleStatus.ACTIVATED) { throw new ExtensionException(ExtensionException.DEPENDENCY_NOT_STARTED, dep, getId()); } checkDependenciesActivated(depBundle); } } } /** * Activate all extension bundles. This method will active all extension * descriptors it contains and is the third phase in an extensions life * cycle (after starting). * * @throws ExtensionException if any bundle could not be activated */ public synchronized void activate() throws ExtensionException { try { if (isContainsPlugin() && getStatus() != ExtensionBundleStatus.STARTED) { throw new ExtensionException(ExtensionException.INVALID_EXTENSION_BUNDLE_STATUS, getId(), "Bundle is not in started so cannot be activated."); } // Check this bundles dependencies are installed and started checkDependenciesActivated(this); ExtensionException ee = null; for (Iterator i = iterator(); i.hasNext();) { ExtensionDescriptor d = (ExtensionDescriptor) i.next(); try { d.activate(); status = ExtensionBundleStatus.ACTIVATED; } catch (ExtensionException ex) { if (ee == null) { ee = ex; } } catch (Throwable t) { if (ee == null) { ee = new ExtensionException(ExtensionException.INTERNAL_ERROR, t); } } } if (ee != null) { throw ee; } } catch (ExtensionException ee) { log.error("Failed to activate extension bundle. ", ee); error = ee; status = ExtensionBundleStatus.ERROR; throw ee; } error = null; } /** * Stop all extension bundles. This method will active all extension * descriptors it contains and is the second or third phase in an extensions * life cycle (after starting or activating). * * @throws ExtensionException if any bundle could not be stopped */ public synchronized void stop() throws ExtensionException { ExtensionException ee = null; try { for (Iterator i = iterator(); i.hasNext();) { ExtensionDescriptor d = (ExtensionDescriptor) i.next(); try { d.stop(); } catch (ExtensionException ex) { if (ee == null) { ee = ex; } log.error("Failed to stop extension bundle. ", ex); } } if (ee != null) { throw ee; } } finally { status = ExtensionBundleStatus.ENABLED; } } /** * Stop all extension bundles. This method will load all extension * descriptors it contains and is the first phase in an extensions life * cycle. * * @throws ExtensionException on any error loading bundles */ public synchronized void load() throws ExtensionException { try { if (log.isInfoEnabled()) { log.info("Loading bundle from " + getFile().getAbsolutePath()); } installer = new ExtensionInstaller(this); SAXBuilder sax = new SAXBuilder(); try { doc = sax.build(descriptor); } catch (JDOMException jde) { jde.printStackTrace(); throw new ExtensionException(ExtensionException.FAILED_TO_PARSE_DESCRIPTOR, jde); } catch (IOException ioe) { throw new ExtensionException(ExtensionException.INTERNAL_ERROR, ioe, "Failed to load descriptor for parsing."); } hidden = "true".equals(doc.getRootElement().getAttributeValue("hidden")); license = doc.getRootElement().getAttributeValue("license"); license = license == null || license.equals("") ? "Unknown" : license; if (log.isDebugEnabled()) log.debug("Application bundle license is " + license); productURL = doc.getRootElement().getAttributeValue("productURL"); instructionsURL = doc.getRootElement().getAttributeValue("instructionsURL"); // Dependencies if any dependencyNames = null; String dependencies = doc.getRootElement().getAttributeValue("dependencies"); if (dependencies != null) { log.warn("DEPRECATED. dependencies attribute in bundle " + getFile().getAbsolutePath() + " should now use 'depends'."); } else { dependencies = doc.getRootElement().getAttributeValue("depends"); } if (!Util.isNullOrTrimmedBlank(dependencies)) { dependencyNames = Arrays.asList(dependencies.split(",")); } // Get the required host version String requiredHostVersionString = doc.getRootElement().getAttributeValue("requiredHostVersion"); if (requiredHostVersionString != null && !"any".equalsIgnoreCase(requiredHostVersionString)) { requiredHostVersion = new VersionInfo.Version(requiredHostVersionString); int dif = requiredHostVersion.compareTo(VersionInfo.getVersion()); if (dif > 0) throw new ExtensionException(ExtensionException.INSUFFICIENT_SSLEXPLORER_HOST_VERSION, getName(), requiredHostVersionString); } else { requiredHostVersion = null; } String ver = doc.getRootElement().getAttributeValue("version"); if (ver == null || ver.equals("")) { throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, "<applications> element requires the attribute 'version'."); } version = new VersionInfo.Version(ver); if (doc.getRootElement().getName().equals("bundle")) { Attribute a = doc.getRootElement().getAttribute("id"); id = a == null ? null : a.getValue(); if (id == null) { throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, "<bundle> element requires attribute 'id'"); } if (log.isDebugEnabled()) log.debug("Application bundle id is " + id); name = doc.getRootElement().getAttribute("name").getValue(); if (log.isDebugEnabled()) log.debug("Application bundle name is " + name); Attribute orderAttr = doc.getRootElement().getAttribute("order"); if (orderAttr == null) { log.warn("<bundle> element in " + getFile().getPath() + " now requires attribute 'order'. Assuming 99999"); order = 99999; } else { try { order = orderAttr.getIntValue(); } catch (DataConversionException dce) { throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, "'order' attribute is invalid. " + dce.getMessage()); } } licenseFilePath = doc.getRootElement().getAttributeValue("licenseAgreement"); if (name == null) { throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, "<bundle> element requires the attribute 'name'"); } startOnSetupMode = "true".equals(doc.getRootElement().getAttributeValue("startOnSetupMode")); for (Iterator i = doc.getRootElement().getChildren().iterator(); i.hasNext();) { Element e = (Element) i.next(); if (e.getName().equalsIgnoreCase("description")) { description = Util.trimmedBothOrBlank(e.getText()); } else if (e.getName().equalsIgnoreCase("install")) { processInstall(e); } else if (e.getName().equalsIgnoreCase("messages")) { // processed later messageElement = e; } else if (e.getName().equals("application") || e.getName().equals("extension")) { ExtensionDescriptor desc = new ExtensionDescriptor(); desc.load(this, e); add(desc); } else { throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, "<bundle> element may only contain <description> or <extension> (or the deprecated <application>) elements."); } } } else if (doc.getRootElement().getName().equals("application") || doc.getRootElement().getName().equals("extension")) { log.warn("DEPRECATED. All extensions should now use the <bundle> tag, " + getFile().getPath() + " is using not using this tag."); ExtensionDescriptor desc = new ExtensionDescriptor(); desc.load(this, doc.getRootElement()); id = desc.getId(); name = desc.getName(); description = desc.getDescription(); order = 99999; dependencyNames = Arrays.asList( new String[] { "sslexplorer-community-applications", "sslexplorer-community-tunnels" }); add(desc); } else { throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, "Application bundle root element must be <bundle> (or the deprecated <application> or <extension>) elements."); } // All we know is that the application is // installed until the application store is // available setType(TYPE_INSTALLED); } catch (ExtensionException ee) { error = ee; throw ee; } error = null; } /** * Get if this bundle contains any plugins. * * @return contains plugins */ public boolean isContainsPlugin() { boolean containsPlugin = false; for (ExtensionDescriptor descriptor : this) { if (descriptor.getTypeName().equals(PluginType.TYPE)) { containsPlugin = true; } } return containsPlugin; } /** * Remove this bundle. If it started, then it will be stopped first. If the * bundle contains plugins the actual removal of the files will be deferred * until restart. * * @throws Exception */ public void removeBundle() throws Exception { if (log.isInfoEnabled()) log.info("Removing extension bundle " + getId()); boolean containsPlugin = isContainsPlugin(); try { // Stop the bundle if no plugins are contained if (!containsPlugin && getStatus().isStartedOrActivated()) { stop(); } // for (ExtensionDescriptor descriptor : this) { descriptor.removeDescriptor(); } } finally { if (containsPlugin) { setType(ExtensionBundle.TYPE_PENDING_REMOVAL); } } } /** * Get the <i>Install</i> (if any) for this extension bundle. * <code>null</code> will be returned if this bundle has no installer. * * @return extension installer */ public ExtensionInstaller getInstaller() { return installer; } public String getInstructionsURL() { return instructionsURL; } public String getProductURL() { return productURL; } public VersionInfo.Version getVersion() { return version; } public VersionInfo.Version getDisplayVersion() { return isUpdateable() ? updateVersion : version; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public void setLicense(String license) { this.license = license; } public String getLicense() { return license; } public int getType() { return type; } public void setType(int type) { this.type = type; } public File getBaseDir() { return getFile() == null ? null : getFile().getParentFile(); } /** * @param application * @return */ public boolean containsApplication(String application) { for (Iterator i = iterator(); i.hasNext();) { if (((ExtensionDescriptor) i.next()).getId().equals(application)) { return true; } } return false; } /** * @param id * @return */ public ExtensionDescriptor getApplicationDescriptor(String id) { for (Iterator i = iterator(); i.hasNext();) { ExtensionDescriptor app = (ExtensionDescriptor) i.next(); if (app.getId().equals(id)) { return app; } } return null; } public int compareTo(Object arg0) { int c = getType() - ((ExtensionBundle) arg0).getType(); return c != 0 ? c : name.compareTo(((ExtensionBundle) arg0).name); } public File getLicenseFile() { File baseDir = getBaseDir(); return baseDir == null || licenseFilePath == null ? null : new File(baseDir, licenseFilePath); } public VersionInfo.Version getRequiredHostVersion() { return requiredHostVersion; } public void setRequiredHostVersion(VersionInfo.Version requiredHostVersion) { this.requiredHostVersion = requiredHostVersion; } public void setCategory(String category) { this.category = category; } public boolean isMandatoryUpdate() { return mandatoryUpdate; } public Throwable getError() { return error; } public boolean canStop() { boolean canStop = status.isStartedOrActivated(); if (!canStop) { return false; } for (Iterator i = iterator(); i.hasNext();) { ExtensionDescriptor d = (ExtensionDescriptor) i.next(); if (!d.canStop()) { return false; } } return true; } public boolean canStart() { return ExtensionBundleStatus.ENABLED.equals(status) && getType() != ExtensionBundle.TYPE_PENDING_INSTALLATION && getType() != ExtensionBundle.TYPE_PENDING_REMOVAL && getType() != ExtensionBundle.TYPE_PENDING_UPDATE && getType() != ExtensionBundle.TYPE_PENDING_STATE_CHANGE; } public boolean canDisable() { return !ExtensionBundleStatus.DISABLED.equals(status) && type != TYPE_CONFIGUREABLE && type != TYPE_INSTALLABLE && type != TYPE_PENDING_STATE_CHANGE; } public boolean canEnable() { return ExtensionBundleStatus.DISABLED.equals(status) && type != TYPE_PENDING_STATE_CHANGE; } public ExtensionBundleStatus getStatus() { return status; } public void setStatus(ExtensionBundleStatus status) { this.status = status; } private void processInstall(Element installElement) throws ExtensionException { String when = installElement.getAttributeValue("when"); when = when == null ? ExtensionInstaller.ON_ACTIVATE : when; for (Iterator i = installElement.getChildren().iterator(); i.hasNext();) { Element e = (Element) i.next(); if (e.getName().equalsIgnoreCase("mkdir")) { String dir = Util.trimmedBothOrBlank(e.getText()); if (dir == null || dir.equals("")) { throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, "<mkdir> contents must be the name of a directory to create."); } installer.addOp(new ExtensionInstaller.MkdirInstallOp(when, dir)); } else if (e.getName().equalsIgnoreCase("cp")) { String from = Util.trimmedBothOrBlank(e.getText()); String to = e.getAttributeValue("to"); String toDir = e.getAttributeValue("toDir"); boolean overwrite = "true".equalsIgnoreCase(e.getAttributeValue("overwrite")); if (from == null || from.equals("") || ((to == null || to.equals("")) && (toDir == null || toDir.equals("")))) { throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, "<cp> content must be the source path and the tag must have either a to or toDir attribute."); } installer.addOp(new ExtensionInstaller.CpInstallOp(when, from, to, toDir, overwrite)); } else if (e.getName().equalsIgnoreCase("rm")) { String path = Util.trimmedBothOrBlank(e.getText()); if (path == null || path.equals("")) { throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, "<rm> must a path as its content."); } installer.addOp(new ExtensionInstaller.RmInstallOp(when, path)); } else if (e.getName().equalsIgnoreCase("custom")) { String clazz = Util.trimmedBothOrBlank(e.getText()); if (clazz == null || clazz.equals("")) { throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, "<custom> must provide a class name that implements ExtensionInstaller.ExtensionInstallOp as its content."); } try { installer.addOp(new ExtensionInstaller.CustomInstallOpWrapper(when, clazz)); } catch (Exception ex) { throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, ex, "Failed to create <custom> install op."); } } } } public String getChanges() { return changes == null ? "" : changes.trim(); } public String toString() { return id + " " + version; } public VersionInfo.Version getUpdateVersion() { return updateVersion; } public void setUpdateVersion(VersionInfo.Version updateVersion) { this.updateVersion = updateVersion; } public void setChanges(String changes) { this.changes = changes; } public boolean isStartOnSetupMode() { return startOnSetupMode; } public String getPlatform() { return platform; } public String getArch() { return arch; } }