You are viewing a plain text version of this content. The canonical link for it is here.
Posted to scm@geronimo.apache.org by am...@apache.org on 2006/04/18 04:49:28 UTC

svn commit: r394827 - in /geronimo/branches/1.1/modules/system/src: java/org/apache/geronimo/system/configuration/ schema/

Author: ammulder
Date: Mon Apr 17 19:49:26 2006
New Revision: 394827

URL: http://svn.apache.org/viewcvs?rev=394827&view=rev
Log:
Support for upgrade lists, obsoletes, etc.
As an example, now you can upgrade a minimal tomcat server to a J2EE tomcat
  server with one command.

Modified:
    geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/configuration/ConfigInstallerGBean.java
    geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/configuration/ConfigurationMetadata.java
    geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/configuration/DownloadPoller.java
    geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/configuration/DownloadResults.java
    geronimo/branches/1.1/modules/system/src/schema/config-list.xsd

Modified: geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/configuration/ConfigInstallerGBean.java
URL: http://svn.apache.org/viewcvs/geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/configuration/ConfigInstallerGBean.java?rev=394827&r1=394826&r2=394827&view=diff
==============================================================================
--- geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/configuration/ConfigInstallerGBean.java (original)
+++ geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/configuration/ConfigInstallerGBean.java Mon Apr 17 19:49:26 2006
@@ -34,6 +34,7 @@
 import java.util.Map;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.jar.JarFile;
 import java.util.jar.JarEntry;
 import javax.security.auth.login.FailedLoginException;
@@ -193,13 +194,18 @@
         return new ConfigurationList(repo, backupURLs, data);
     }
 
-    private ConfigurationMetadata processConfiguration(Element config) {
+    private ConfigurationMetadata processConfiguration(Element config) throws SAXException {
         String configId = getChildText(config, "config-id");
         NodeList licenseNodes = config.getElementsByTagName("license");
         ConfigurationMetadata.License[] licenses = new ConfigurationMetadata.License[licenseNodes.getLength()];
         for(int j=0; j<licenseNodes.getLength(); j++) {
             Element node = (Element) licenseNodes.item(j);
-            licenses[j] = new ConfigurationMetadata.License(getText(node), Boolean.valueOf(node.getAttribute("osi-approved")).booleanValue());
+            String licenseName = getText(node);
+            String openSource = node.getAttribute("osi-approved");
+            if(licenseName == null || licenseName.equals("") || openSource == null || openSource.equals("")) {
+                throw new SAXException("Invalid config file: license name and osi-approved flag required");
+            }
+            licenses[j] = new ConfigurationMetadata.License(licenseName, Boolean.valueOf(openSource).booleanValue());
         }
         boolean eligible = true;
         NodeList preNodes = config.getElementsByTagName("prerequisite");
@@ -207,6 +213,9 @@
         for(int j=0; j<preNodes.getLength(); j++) {
             Element node = (Element) preNodes.item(j);
             String originalConfigId = getChildText(node, "id");
+            if(originalConfigId == null) {
+                throw new SAXException("Prerequisite requires <id>");
+            }
             Artifact artifact = Artifact.create(originalConfigId.replaceAll("\\*", ""));
             boolean present = resolver.queryArtifacts(artifact).length > 0;
             prereqs[j] = new ConfigurationMetadata.Prerequisite(artifact, present,
@@ -222,6 +231,9 @@
             boolean match = false;
             for (int j = 0; j < gerVersions.length; j++) {
                 String gerVersion = gerVersions[j];
+                if(gerVersion == null || gerVersion.equals("")) {
+                    throw new SAXException("geronimo-version should not be empty!");
+                }
                 if(gerVersion.equals(version)) {
                     match = true;
                     break;
@@ -235,6 +247,9 @@
             boolean match = false;
             for (int j = 0; j < jvmVersions.length; j++) {
                 String jvmVersion = jvmVersions[j];
+                if(jvmVersion == null || jvmVersion.equals("")) {
+                    throw new SAXException("jvm-version should not be empty!");
+                }
                 if(version.startsWith(jvmVersion)) {
                     match = true;
                     break;
@@ -242,8 +257,12 @@
             }
             if(!match) eligible = false;
         }
-        Artifact artifact = Artifact.create(configId);
-        boolean installed = configManager.isLoaded(artifact);
+        Artifact artifact = null;
+        boolean installed = false;
+        if (configId != null) {
+            artifact = Artifact.create(configId);
+            installed = configManager.isLoaded(artifact);
+        }
         log.trace("Checking "+configId+": installed="+installed+", eligible="+eligible);
         ConfigurationMetadata data = new ConfigurationMetadata(artifact, getChildText(config, "name"),
                 getChildText(config, "description"), getChildText(config, "category"), installed, eligible);
@@ -251,7 +270,19 @@
         data.setJvmVersions(jvmVersions);
         data.setLicenses(licenses);
         data.setPrerequisites(prereqs);
-        data.setDependencies(getChildrenText(config, "dependency"));
+        NodeList list = config.getElementsByTagName("dependency");
+        List start = new ArrayList();
+        String deps[] = new String[list.getLength()];
+        for(int i=0; i<list.getLength(); i++) {
+            Element node = (Element) list.item(i);
+            deps[i] = getText(node);
+            if(node.hasAttribute("start") && node.getAttribute("start").equalsIgnoreCase("true")) {
+                start.add(deps[i]);
+            }
+        }
+        data.setDependencies(deps);
+        data.setForceStart((String[]) start.toArray(new String[start.size()]));
+        data.setObsoletes(getChildrenText(config, "obsoletes"));
         return data;
     }
 
@@ -313,17 +344,71 @@
 
     public void install(ConfigurationList list, String username, String password, DownloadPoller poller) {
         try {
-            // For each configuration in the list to install...
+            // Step 1: validate everything
             for (int i = 0; i < list.getConfigurations().length; i++) {
-                // 1. Identify the configuration
                 ConfigurationMetadata metadata = list.getConfigurations()[i];
-                // 2. Validate that we can install it
                 validateConfiguration(metadata);
+            }
+
+            // Step 2: everything is valid, do the installation
+            for (int i = 0; i < list.getConfigurations().length; i++) {
+                // 1. Identify the configuration
+                ConfigurationMetadata metadata = list.getConfigurations()[i];
+                // 2. Unload obsoleted configurations
+                List obsoletes = new ArrayList();
+                for (int j = 0; j < metadata.getObsoletes().length; j++) {
+                    String name = metadata.getObsoletes()[j];
+                    Artifact obsolete = Artifact.create(name);
+                    if(configManager.isLoaded(obsolete)) {
+                        if(configManager.isRunning(obsolete)) {
+                            configManager.stopConfiguration(obsolete);
+                        }
+                        configManager.unloadConfiguration(obsolete);
+                        obsoletes.add(obsolete);
+                    }
+                }
                 // 3. Download the artifact if necessary, and its dependencies
-                downloadArtifact(metadata.getConfigId(), list.getMainRepository(), list.getBackupRepositories(),
-                        username, password, new ResultsFileWriteMonitor(poller));
-                // 4. Installation of this configuration finished successfully
-                poller.addInstalledConfigID(metadata.getConfigId());
+                Set working = new HashSet();
+                if(metadata.getConfigId() != null) {
+                    downloadArtifact(metadata.getConfigId(), list.getMainRepository(), list.getBackupRepositories(),
+                            username, password, new ResultsFileWriteMonitor(poller), working);
+                    poller.addInstalledConfigID(metadata.getConfigId());
+                } else {
+                    String[] deps = metadata.getDependencies();
+                    for (int j = 0; j < deps.length; j++) {
+                        String dep = deps[j];
+                        Artifact entry = Artifact.create(dep);
+                        if(configManager.isRunning(entry)) {
+                            continue;
+                        }
+                        downloadArtifact(entry, list.getMainRepository(), list.getBackupRepositories(),
+                                username, password, new ResultsFileWriteMonitor(poller), working);
+                    }
+                }
+                // 4. Uninstall obsolete configurations
+                for (int j = 0; j < obsoletes.size(); j++) {
+                    Artifact artifact = (Artifact) obsoletes.get(j);
+                    configManager.uninstallConfiguration(artifact);
+                }
+                // 5. Installation of this configuration finished successfully
+                if(metadata.getConfigId() != null) {
+                    poller.addInstalledConfigID(metadata.getConfigId());
+                }
+            }
+
+            // Step 3: Start anything that's marked accordingly
+            for (int i = 0; i < list.getConfigurations().length; i++) {
+                ConfigurationMetadata metadata = list.getConfigurations()[i];
+                for (int j = 0; j < metadata.getForceStart().length; j++) {
+                    String id = metadata.getForceStart()[j];
+                    Artifact artifact = Artifact.create(id);
+                    if(configManager.isConfiguration(artifact)) {
+                        poller.setCurrentFilePercent(-1);
+                        poller.setCurrentMessage("Starting "+artifact);
+                        configManager.loadConfiguration(artifact);
+                        configManager.startConfiguration(artifact);
+                    }
+                }
             }
         } catch (Exception e) {
             poller.setFailure(e);
@@ -346,16 +431,16 @@
             // 2. Validate that we can install this
             validateConfiguration(data.getConfiguration());
             
-            // 3. Install the CAR into the repository
-            ResultsFileWriteMonitor monitor = new ResultsFileWriteMonitor(poller);
-            writeableRepo.copyToRepository(carFile, data.getConfiguration().getConfigId(), monitor);
-
-            // 4. Download all the dependencies
-            downloadArtifact(data.getConfiguration().getConfigId(), data.getRepository(), data.getBackups(),
-                    username, password, monitor);
+            // 3. Install the CAR into the repository (it shouldn't be re-downloaded)
+            if(data.getConfiguration().getConfigId() != null) {
+                ResultsFileWriteMonitor monitor = new ResultsFileWriteMonitor(poller);
+                writeableRepo.copyToRepository(carFile, data.getConfiguration().getConfigId(), monitor);
+            }
 
-            // 5. Installation of the main configuration finished successfully
-            poller.addInstalledConfigID(data.getConfiguration().getConfigId());
+            // 4. Use the standard logic to remove obsoletes, install dependencies, etc.
+            //    This will validate all over again (oh, well)
+            install(new ConfigurationList(data.getRepository(), data.getBackups(), new ConfigurationMetadata[]{data.getConfiguration()}),
+                    username, password, poller);
         } catch (Exception e) {
             poller.setFailure(e);
         } finally {
@@ -365,8 +450,18 @@
 
     private void validateConfiguration(ConfigurationMetadata metadata) throws MissingDependencyException {
         // 1. Check that it's not already running
-        if(configManager.isRunning(metadata.getConfigId())) {
-            throw new IllegalArgumentException("Configuration "+metadata.getConfigId()+" is already running!");
+        if(metadata.getConfigId() != null) { // that is, it's a real configuration not a plugin list
+            if(configManager.isRunning(metadata.getConfigId())) {
+                throw new IllegalArgumentException("Configuration "+metadata.getConfigId()+" is already running!");
+            }
+        } else { // Different validation for plugin lists
+            for (int i = 0; i < metadata.getDependencies().length; i++) {
+                String dep = metadata.getDependencies()[i];
+                Artifact artifact = Artifact.create(dep);
+                if(!artifact.isResolved()) {
+                    throw new MissingDependencyException("Configuration list "+metadata.getName()+" may not use partal artifact names for dependencies ("+dep+")");
+                }
+            }
         }
         // 2. Check that we meet the prerequisites
         ConfigurationMetadata.Prerequisite[] prereqs = metadata.getPrerequisites();
@@ -442,7 +537,12 @@
      *                                     are not accepted
      * @throws MissingDependencyException  When a dependency cannot be located in any of the listed repositories
      */
-    private void downloadArtifact(Artifact configID, URL repoURL, URL[] backups, String username, String password, ResultsFileWriteMonitor monitor) throws IOException, FailedLoginException, MissingDependencyException {
+    private void downloadArtifact(Artifact configID, URL repoURL, URL[] backups, String username, String password, ResultsFileWriteMonitor monitor, Set soFar) throws IOException, FailedLoginException, MissingDependencyException {
+        if(soFar.contains(configID)) {
+            return; // Avoid enless work due to circular dependencies
+        } else {
+            soFar.add(configID);
+        }
         //todo: check all repositories?
         if(!writeableRepo.contains(configID)) {
             InputStream in = openStream(configID, repoURL, backups, username, password, monitor);
@@ -469,7 +569,7 @@
             for (int i = 0; i < dependencies.length; i++) {
                 Dependency dep = dependencies[i];
                 Artifact artifact = dep.getArtifact();
-                downloadArtifact(artifact, repoURL, backups, username, password, monitor);
+                downloadArtifact(artifact, repoURL, backups, username, password, monitor, soFar);
             }
         } catch (NoSuchConfigException e) {
             throw new IllegalStateException("Installed configuration into repository but ConfigStore does not see it: "+e.getMessage());
@@ -496,7 +596,7 @@
      * Used to get dependencies for a Configuration
      */
     private static Dependency[] getDependencies(ConfigurationData data) {
-        List dependencies = data.getEnvironment().getDependencies();
+        List dependencies = new ArrayList(data.getEnvironment().getDependencies());
         Collection children = data.getChildConfigurations().values();
         for (Iterator it = children.iterator(); it.hasNext();) {
             ConfigurationData child = (ConfigurationData) it.next();
@@ -511,6 +611,8 @@
 
     private static InputStream openStream(Artifact artifact, URL repo, URL[] backups, String username, String password, ResultsFileWriteMonitor monitor) throws IOException, FailedLoginException, MissingDependencyException {
         if(monitor != null) {
+            monitor.getResults().setCurrentFilePercent(-1);
+            monitor.getResults().setCurrentMessage("Attempting to download "+artifact);
             monitor.setTotalBytes(-1); // In case the server doesn't say
         }
         InputStream in;

Modified: geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/configuration/ConfigurationMetadata.java
URL: http://svn.apache.org/viewcvs/geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/configuration/ConfigurationMetadata.java?rev=394827&r1=394826&r2=394827&view=diff
==============================================================================
--- geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/configuration/ConfigurationMetadata.java (original)
+++ geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/configuration/ConfigurationMetadata.java Mon Apr 17 19:49:26 2006
@@ -33,9 +33,11 @@
     private final boolean installed;
     private final boolean eligible;
     private String[] dependencies;
+    private String[] obsoletes;
     private License[] licenses;
     private String[] geronimoVersions;
     private String[] jvmVersions;
+    private String[] forceStart;
     private Prerequisite[] prerequisites;
 
     public ConfigurationMetadata(Artifact configId, String name, String description, String category, boolean installed, boolean eligible) {
@@ -51,6 +53,14 @@
         this.dependencies = dependencies;
     }
 
+    public void setObsoletes(String[] obsoletes) {
+        this.obsoletes = obsoletes;
+    }
+
+    public void setForceStart(String[] forceStart) {
+        this.forceStart = forceStart;
+    }
+
     /**
      * Gets the Config ID for this configuration, which is a globally unique
      * identifier.
@@ -92,11 +102,28 @@
 
     /**
      * Gets the JAR or configuration dependencies for this configuration,  Each
-     * String in the result is an Artifact (or Config ID) in String form.  The
-     * dependency names may be partial artifact names
+     * String in the result is an Artifact (or Config ID) in String form.
+     * Generally speaking, the dependency names may be partial artifact names
+     * (but not, for example, if this whole thing is a plugin list).
      */
     public String[] getDependencies() {
         return dependencies;
+    }
+
+    /**
+     * Gets the configurations obsoleted by this configuration.  Each
+     * String in the result is an Artifact (or Config ID) in String form.
+     */
+    public String[] getObsoletes() {
+        return obsoletes;
+    }
+
+    /**
+     * Gets the configurations that should definitely be started when the
+     * install process completes.
+     */
+    public String[] getForceStart() {
+        return forceStart;
     }
 
     public String[] getGeronimoVersions() {

Modified: geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/configuration/DownloadPoller.java
URL: http://svn.apache.org/viewcvs/geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/configuration/DownloadPoller.java?rev=394827&r1=394826&r2=394827&view=diff
==============================================================================
--- geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/configuration/DownloadPoller.java (original)
+++ geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/configuration/DownloadPoller.java Mon Apr 17 19:49:26 2006
@@ -22,27 +22,88 @@
  * An interface for callers who want to monitor the progress of an installation.
  * These are all callbacks sent by the server.
  *
+ * @see ConfigurationInstaller
+ *
  * @version $Rev: 46019 $ $Date: 2004-09-14 05:56:06 -0400 (Tue, 14 Sep 2004) $
  */
 public interface DownloadPoller {
-    void addInstalledConfigID(Artifact dep);
-    
+    /**
+     * Notes a configuration that was removed because it was obsoleted by a
+     * newly-installed configuration.
+     */
+    void addRemovedConfigID(Artifact obsolete);
+
+    /**
+     * Notes that a configuration passed as an argument to the install
+     * operation was successfully installed.  This will only be called on
+     * the original arguments to the install command, not on anything
+     * installed because it was a dependency.
+     */
+    void addInstalledConfigID(Artifact target);
+
+    /**
+     * Notes that a configuration was restarted as a result of the
+     * current operation.  This usually means that it depended on a
+     * configuration that was obsoleted (removed), so it shut down when
+     * the remove happened, and was started up again after the replacement
+     * was installed.
+     */
+    void addRestartedConfigID(Artifact target);
+
+    /**
+     * Notes that the current install operation found a dependency, and that
+     * dependency was satisfied by an artifact already available in the
+     * current server environment.
+     */
     void addDependencyPresent(Artifact dep);
 
+    /**
+     * Notes that the current install operation found a dependency, and that
+     * dependency was downloaded from a remote repository and installed into
+     * the local server environment.
+     */
     void addDependencyInstalled(Artifact dep);
 
+    /**
+     * Indicates which file the configuration installer is working on at the
+     * moment.  Mainly for purposes of user feedback during asynchronous
+     * requests.
+     */
     void setCurrentFile(String currentFile);
 
+    /**
+     * Describes the current operation status as a text message.  Mainly for
+     * purposes of user feedback during asynchronous requests.
+     */
     void setCurrentMessage(String currentMessage);
 
+    /**
+     * Gives the percent complete for a file currently being downloaded.
+     * Mainly for purposes of user feedback during asynchronous requests.
+     * This may be -1 if the download server does not supply the file size in
+     * advance.
+     */
     void setCurrentFilePercent(int currentFileProgress);
 
+    /**
+     * Called at the end of a file download with the number of bytes downloaded
+     * in the current operation.  This can be used to calculate a rough
+     * transfer rate (the time between setCurrentFile and setDownloadBytes) as
+     * well as if the caller wants to total the size of all downloads for the
+     * current installation.
+     */
     void addDownloadBytes(long bytes);
 
+    /**
+     * Indicates that a failure was encountered during the installation
+     * operation.  Any failure is currently treated as fatal -- the installer
+     * will not attempt to complete additional tasks after a failure.
+     */
     void setFailure(Exception failure);
 
     /**
-     * This will be called even in the event of a failure.
+     * Always called when the operation is complete, regardless of whether
+     * there was a failure or not.
      */
     void setFinished();
 }

Modified: geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/configuration/DownloadResults.java
URL: http://svn.apache.org/viewcvs/geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/configuration/DownloadResults.java?rev=394827&r1=394826&r2=394827&view=diff
==============================================================================
--- geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/configuration/DownloadResults.java (original)
+++ geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/configuration/DownloadResults.java Mon Apr 17 19:49:26 2006
@@ -28,6 +28,8 @@
  * @version $Rev: 46019 $ $Date: 2004-09-14 05:56:06 -0400 (Tue, 14 Sep 2004) $
  */
 public class DownloadResults implements Serializable, DownloadPoller {
+    private List removedConfigIDs = new ArrayList();
+    private List restartedConfigIDs = new ArrayList();
     private List installedConfigIDs = new ArrayList();
     private List dependenciesPresent = new ArrayList();
     private List dependenciesInstalled = new ArrayList();
@@ -42,6 +44,14 @@
         installedConfigIDs.add(dep);
     }
 
+    public void addRemovedConfigID(Artifact obsolete) {
+        removedConfigIDs.add(obsolete);
+    }
+
+    public void addRestartedConfigID(Artifact target) {
+        restartedConfigIDs.add(target);
+    }
+
     public void addDependencyPresent(Artifact dep) {
         dependenciesPresent.add(dep);
     }
@@ -63,6 +73,7 @@
     }
 
     public void setFailure(Exception failure) {
+failure.printStackTrace();
         this.failure = failure;
     }
 
@@ -106,6 +117,14 @@
      */
     public Artifact[] getInstalledConfigIDs() {
         return (Artifact[]) installedConfigIDs.toArray(new Artifact[installedConfigIDs.size()]);
+    }
+
+    public Artifact[] getRemovedConfigIDs() {
+        return (Artifact[]) removedConfigIDs.toArray(new Artifact[installedConfigIDs.size()]);
+    }
+
+    public Artifact[] getRestartedConfigIDs() {
+        return (Artifact[]) restartedConfigIDs.toArray(new Artifact[installedConfigIDs.size()]);
     }
 
     /**

Modified: geronimo/branches/1.1/modules/system/src/schema/config-list.xsd
URL: http://svn.apache.org/viewcvs/geronimo/branches/1.1/modules/system/src/schema/config-list.xsd?rev=394827&r1=394826&r2=394827&view=diff
==============================================================================
--- geronimo/branches/1.1/modules/system/src/schema/config-list.xsd (original)
+++ geronimo/branches/1.1/modules/system/src/schema/config-list.xsd Mon Apr 17 19:49:26 2006
@@ -103,11 +103,18 @@
                     </xs:documentation>
                 </xs:annotation>
             </xs:element>
-            <xs:element name="config-id" type="xs:string">
+            <xs:element name="config-id" type="xs:string" minOccurs="0">
                 <xs:annotation>
                     <xs:documentation>
                         The Geronimo configId for this configuration, which uniquely identifies
-                        it, and also is used to construct a path to download it if necessary
+                        it, and also is used to construct a path to download it if necessary.
+
+                        If no config-id is provided, that means this is a plugin group, which is
+                        just a list of other plugins to install.  The prerequisites must still be
+                        met, but then the dependencies listed for this configuration will be
+                        treated as the list of actual configuraitons to install.  NOTE: for plugin
+                        lists, each of the dependencies must be a full config-id; entries without
+                        a version number for example will not work.
                     </xs:documentation>
                 </xs:annotation>
             </xs:element>
@@ -234,6 +241,28 @@
                     </xs:documentation>
                 </xs:annotation>
             </xs:element>
+            <xs:element name="obsoletes" type="xs:string" minOccurs="0" maxOccurs="unbounded">
+                <xs:annotation>
+                    <xs:documentation>
+                        The configId of another configuration that this configuration replaces.
+                        That can be used, for example, to replace a less functional configuration
+                        with a more functional one, or to upgrade a component to a newer version.
+
+                        This is applied as an exact match, except that the version number may be
+                        omitted, in which case any matching version of the configuration will be
+                        replaced.  Be aware that omitting the version number may cause a
+                        configuration to be downgraded, so you may choose to explicitly list all
+                        lesser versions.  That may not be desirable either, though.  If the
+                        server has foo-1.0.2 installed and you install foo-1.0.1, would you
+                        rather have it downgrade or end up with both installed?
+
+                        NOTE: currently the "obsoletes" entries are only processed on the
+                        configuration(s) passed directly to the ConfigurationInstaller (not on
+                        dependencies that are brought down as a result).  That means that a
+                        plugin group must list obsoletes for all its components, etc.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:element>
         </xs:sequence>
     </xs:complexType>
 
@@ -261,6 +290,34 @@
         </xs:simpleContent>
     </xs:complexType>
 
+    <xs:complexType name="dependencyType">
+        <xs:annotation>
+            <xs:documentation>
+                See configurationType/dependency above
+            </xs:documentation>
+        </xs:annotation>
+
+        <xs:simpleContent>
+            <xs:extension base="xs:string">
+                <xs:attribute name="start" use="optional" type="xs:boolean" default="false">
+                    <xs:annotation>
+                        <xs:documentation>
+                            Normally when a configuration dependency is installed it will not
+                            be started.  The user may be prompted to start the configuration
+                            with whatever tool kicked off the install to begin with.
+
+                            This attribute may be set to "true" to force the dependency to
+                            be started after it is installed.  NOTE: this currently only
+                            affects the dependencies of the configurations actually passed to
+                            the Configuration Installer (not nested dependencies).  This
+                            means it's primarily useful for plugin lists.
+                        </xs:documentation>
+                    </xs:annotation>
+                </xs:attribute>
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
     <xs:complexType name="prerequisiteType">
         <xs:sequence>
             <xs:element name="id" type="xs:string">
@@ -280,7 +337,7 @@
                     </xs:documentation>
                 </xs:annotation>
             </xs:element>
-            <xs:element name="resource-type" type="xs:string">
+            <xs:element name="resource-type" type="xs:string" minOccurs="0">
                 <xs:annotation>
                     <xs:documentation>
                         Describes the type of resource that the prerequisite is. Examples
@@ -309,7 +366,7 @@
                     </xs:documentation>
                 </xs:annotation>
             </xs:element>
-            <xs:element name="description" type="xs:string">
+            <xs:element name="description" type="xs:string" minOccurs="0">
                 <xs:annotation>
                     <xs:documentation>
                         A description for the user about why this is a prerequisite and what