geronimo-scm mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ammul...@apache.org
Subject svn commit: r399534 [3/4] - in /geronimo/branches/1.1: applications/console-standard/src/java/org/apache/geronimo/console/car/ applications/console-standard/src/webapp/WEB-INF/view/car/ configs/console-jetty/src/conf/ configs/console-tomcat/src/conf/ c...
Date Thu, 04 May 2006 04:09:23 GMT
Copied: geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/plugin/PluginInstallerGBean.java (from r399072, 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/plugin/PluginInstallerGBean.java?p2=geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/plugin/PluginInstallerGBean.java&p1=geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/configuration/ConfigInstallerGBean.java&r1=399072&r2=399534&rev=399534&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/plugin/PluginInstallerGBean.java Wed May  3 21:09:17 2006
@@ -14,10 +14,11 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
-package org.apache.geronimo.system.configuration;
+package org.apache.geronimo.system.plugin;
 
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.HttpURLConnection;
@@ -36,9 +37,9 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.SortedSet;
-import java.util.zip.ZipEntry;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
 import javax.security.auth.login.FailedLoginException;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
@@ -67,8 +68,9 @@
 import org.apache.geronimo.kernel.repository.ImportType;
 import org.apache.geronimo.kernel.repository.MissingDependencyException;
 import org.apache.geronimo.kernel.repository.Repository;
-import org.apache.geronimo.kernel.repository.WritableListableRepository;
 import org.apache.geronimo.kernel.repository.Version;
+import org.apache.geronimo.kernel.repository.WritableListableRepository;
+import org.apache.geronimo.system.configuration.ConfigurationStoreUtil;
 import org.apache.geronimo.system.serverinfo.ServerInfo;
 import org.apache.geronimo.system.threads.ThreadPool;
 import org.apache.geronimo.util.encoders.Base64;
@@ -77,7 +79,9 @@
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 import org.xml.sax.Attributes;
+import org.xml.sax.ErrorHandler;
 import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
 import org.xml.sax.helpers.DefaultHandler;
 
 /**
@@ -85,8 +89,8 @@
  *
  * @version $Rev: 46019 $ $Date: 2004-09-14 05:56:06 -0400 (Tue, 14 Sep 2004) $
  */
-public class ConfigInstallerGBean implements ConfigurationInstaller {
-    private final static Log log = LogFactory.getLog(ConfigInstallerGBean.class);
+public class PluginInstallerGBean implements PluginInstaller {
+    private final static Log log = LogFactory.getLog(PluginInstallerGBean.class);
     private static int counter;
     private ConfigurationManager configManager;
     private WritableListableRepository writeableRepo;
@@ -96,7 +100,7 @@
     private Map asyncKeys;
     private ThreadPool threadPool;
 
-    public ConfigInstallerGBean(ConfigurationManager configManager, WritableListableRepository repository, ConfigurationStore configStore, ServerInfo serverInfo, ThreadPool threadPool) {
+    public PluginInstallerGBean(ConfigurationManager configManager, WritableListableRepository repository, ConfigurationStore configStore, ServerInfo serverInfo, ThreadPool threadPool) {
         this.configManager = configManager;
         this.writeableRepo = repository;
         this.configStore = configStore;
@@ -106,14 +110,13 @@
         asyncKeys = Collections.synchronizedMap(new HashMap());
     }
 
-    private static Object getNextKey() {
-        int value;
-        synchronized(ConfigInstallerGBean.class) {
-            value = ++counter;
-        }
-        return new Integer(value);
-    }
-
+    /**
+     * Lists the plugins installed in the local Geronimo server, by name and
+     * ID.
+     *
+     * @return A Map with key type String (plugin name) and value type Artifact
+     *         (config ID of the plugin).
+     */
     public Map getInstalledPlugins() {
         SortedSet artifacts = writeableRepo.list();
 
@@ -156,8 +159,17 @@
         return plugins;
     }
 
-    public ConfigurationArchiveData getPluginMetadata(Artifact configId) {
-        File dir = writeableRepo.getLocation(configId);
+    /**
+     * Gets a CofigurationMetadata for a configuration installed in the local
+     * server.  Should load a saved one if available, or else create a new
+     * default one to the best of its abilities.
+     *
+     * @param moduleId Identifies the configuration.  This must match a
+     *                 configuration currently installed in the local server.
+     *                 The configId must be fully resolved (isResolved() == true)
+     */
+    public PluginMetadata getPluginMetadata(Artifact moduleId) {
+        File dir = writeableRepo.getLocation(moduleId);
         Document doc;
         ConfigurationData configData;
         String source = dir.getAbsolutePath();
@@ -168,73 +180,92 @@
                     return null;
                 }
                 File xml = new File(meta, "geronimo-plugin.xml");
-                configData = configStore.loadConfiguration(configId);
+                configData = configStore.loadConfiguration(moduleId);
                 if(!xml.isFile() || !xml.canRead() || xml.length() == 0) {
-                    return new ConfigurationArchiveData(null, new URL[0], createDefaultMetadata(configData));
+                    return createDefaultMetadata(configData);
                 }
                 source = xml.getAbsolutePath();
-                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-                DocumentBuilder builder = factory.newDocumentBuilder();
+                DocumentBuilder builder = createDocumentBuilder();
                 doc = builder.parse(xml);
             } else {
                 if(!dir.isFile() || !dir.canRead()) {
                     throw new IllegalStateException("Cannot read configuration "+dir.getAbsolutePath());
                 }
-                configData = configStore.loadConfiguration(configId);
+                configData = configStore.loadConfiguration(moduleId);
                 JarFile jar = new JarFile(dir);
                 try {
                     ZipEntry entry = jar.getEntry("META-INF/geronimo-plugin.xml");
                     if(entry == null) {
-                        return new ConfigurationArchiveData(null, new URL[0], createDefaultMetadata(configData));
+                        return createDefaultMetadata(configData);
                     }
                     source = dir.getAbsolutePath()+"#META-INF/geronimo-plugin.xml";
                     InputStream in = jar.getInputStream(entry);
-                    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-                    DocumentBuilder builder = factory.newDocumentBuilder();
+                    DocumentBuilder builder = createDocumentBuilder();
                     doc = builder.parse(in);
                     in.close();
                 } finally {
                     jar.close();
                 }
             }
-            ConfigurationArchiveData result = loadConfigurationMetadata(doc, source);
-            overrideDependencies(configData, result.getConfiguration());
+            PluginMetadata result = loadPluginMetadata(doc, source);
+            overrideDependencies(configData, result);
             return result;
         } catch (InvalidConfigException e) {
-            log.warn("Unable to generate metadata for "+configId, e);
+            log.warn("Unable to generate metadata for "+moduleId, e);
         } catch (Exception e) {
             log.warn("Invalid XML at "+source, e);
         }
         return null;
     }
 
-    public void updatePluginMetadata(ConfigurationArchiveData metadata) {
-        File dir = writeableRepo.getLocation(metadata.getConfiguration().getConfigId());
+    /**
+     * Saves a ConfigurationMetadata for a particular plugin, if the server is
+     * able to record it.  This can be used if you later re-export the plugin,
+     * or just want to review the information for a particular installed
+     * plugin.
+     *
+     * @param metadata The data to save.  The contained configId (which must
+     *                 be fully resolved) identifies the configuration to save
+     *                 this for.
+     */
+    public void updatePluginMetadata(PluginMetadata metadata) {
+        File dir = writeableRepo.getLocation(metadata.getModuleId());
         if(dir == null) {
-            throw new IllegalArgumentException(metadata.getConfiguration().getConfigId()+" is not installed!");
+            throw new IllegalArgumentException(metadata.getModuleId()+" is not installed!");
         }
         File meta = new File(dir, "META-INF");
         if(!meta.isDirectory() || !meta.canRead()) {
-            throw new IllegalArgumentException(metadata.getConfiguration().getConfigId()+" is not a plugin!");
+            throw new IllegalArgumentException(metadata.getModuleId()+" is not a plugin!");
         }
         File xml = new File(meta, "geronimo-plugin.xml");
         try {
             if(!xml.isFile()) {
                 if(!xml.createNewFile()) {
-                    throw new RuntimeException("Cannot create plugin metadata file for "+metadata.getConfiguration().getConfigId());
+                    throw new RuntimeException("Cannot create plugin metadata file for "+metadata.getModuleId());
                 }
             }
-            Document doc = writeConfigurationMetadata(metadata);
+            Document doc = writePluginMetadata(metadata);
             TransformerFactory xfactory = TransformerFactory.newInstance();
             Transformer xform = xfactory.newTransformer();
             xform.setOutputProperty(OutputKeys.INDENT, "yes");
+            xform.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
             xform.transform(new DOMSource(doc), new StreamResult(xml));
         } catch (Exception e) {
-            log.error("Unable to save plugin metadata for "+metadata.getConfiguration().getConfigId(), e);
+            log.error("Unable to save plugin metadata for "+metadata.getModuleId(), e);
         }
     }
 
-    public ConfigurationList listConfigurations(URL mavenRepository, String username, String password) throws IOException, FailedLoginException {
+    /**
+     * Lists the plugins available for download in a particular Geronimo repository.
+     *
+     * @param mavenRepository The base URL to the maven repository.  This must
+     *                        contain the file geronimo-plugins.xml
+     * @param username Optional username, if the maven repo uses HTTP Basic authentication.
+     *                 Set this to null if no authentication is required.
+     * @param password Optional password, if the maven repo uses HTTP Basic authentication.
+     *                 Set this to null if no authentication is required.
+     */
+    public PluginList listPlugins(URL mavenRepository, String username, String password) throws IOException, FailedLoginException {
         String repository = mavenRepository.toString();
         if(!repository.endsWith("/")) {
             repository = repository+"/";
@@ -243,8 +274,8 @@
         URL url = new URL(repository+"geronimo-plugins.xml");
         try {
             //todo: use a progress monitor
-            InputStream in = openStream(null, url, new URL[0], username, password, null).getStream();
-            return loadConfiguration(mavenRepository, in);
+            InputStream in = openStream(null, new URL[]{url}, username, password, null).getStream();
+            return loadPluginList(mavenRepository, in);
         } catch (MissingDependencyException e) {
             log.error("Cannot find plugin index at site "+url);
             return null;
@@ -254,243 +285,54 @@
         }
     }
 
-    private ConfigurationArchiveData loadConfigurationArchive(File file) throws IOException, ParserConfigurationException, SAXException {
-        if(!file.canRead()) {
-            log.error("Cannot read from downloaded CAR file "+file.getAbsolutePath());
-            return null;
-        }
-        JarFile jar = new JarFile(file);
-        Document doc;
-        try {
-            JarEntry entry = jar.getJarEntry("META-INF/geronimo-plugin.xml");
-            if(entry == null) {
-                log.error("Downloaded CAR file does not contain META-INF/geronimo-plugin.xml file");
-                jar.close();
-                return null;
-            }
-            InputStream in = jar.getInputStream(entry);
-            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-            DocumentBuilder builder = factory.newDocumentBuilder();
-            doc = builder.parse(in);
-            in.close();
-        } finally {
-            jar.close();
-        }
-        return loadConfigurationMetadata(doc, file.getAbsolutePath());
-    }
-
-    private ConfigurationArchiveData loadConfigurationMetadata(Document doc, String file) throws SAXException, MalformedURLException {
-        Element root = doc.getDocumentElement();
-        NodeList configs = root.getElementsByTagName("configuration");
-        if(configs.getLength() != 1) {
-            log.error("Configuration archive "+file+" does not have exactly one configuration in META-INF/geronimo-plugin.xml");
-            return null;
-        }
-        ConfigurationMetadata data = processConfiguration((Element) configs.item(0));
-        String repo = getChildText(root, "source-repository");
-        URL repoURL;
-        if(repo == null || repo.equals("")) {
-            log.warn("Configuration archive "+file+" does not list a repository for downloading dependencies.");
-            repoURL = null;
-        } else {
-            repoURL = new URL(repo);
-        }
-        String[] others = getChildrenText(root, "backup-repository");
-        URL[] backups = new URL[others.length];
-        for (int i = 0; i < backups.length; i++) {
-            backups[i] = new URL(others[i]);
-        }
-        return new ConfigurationArchiveData(repoURL, backups, data);
-    }
-
-    private ConfigurationList loadConfiguration(URL repo, InputStream in) throws ParserConfigurationException, IOException, SAXException {
-        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-        DocumentBuilder builder = factory.newDocumentBuilder();
-        Document doc = builder.parse(in);
-        in.close();
-        Element root = doc.getDocumentElement();
-        NodeList configs = root.getElementsByTagName("configuration");
-        List results = new ArrayList();
-        for (int i = 0; i < configs.getLength(); i++) {
-            Element config = (Element) configs.item(i);
-            ConfigurationMetadata data = processConfiguration(config);
-            results.add(data);
-        }
-        String[] backups = getChildrenText(root, "backup-repository");
-        URL[] backupURLs = new URL[backups.length];
-        for(int i = 0; i < backups.length; i++) {
-            if(backups[i].endsWith("/")) {
-                backupURLs[i] = new URL(backups[i]);
-            } else {
-                backupURLs[i] = new URL(backups[i]+"/");
-            }
-        }
-
-        ConfigurationMetadata[] data = (ConfigurationMetadata[]) results.toArray(new ConfigurationMetadata[results.size()]);
-        return new ConfigurationList(repo, backupURLs, data);
-    }
-
-    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);
-            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");
-        ConfigurationMetadata.Prerequisite[] prereqs = new ConfigurationMetadata.Prerequisite[preNodes.getLength()];
-        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,
-                    getChildText(node, "resource-type"), getChildText(node, "description"));
-            if(!present) {
-                log.debug(configId+" is not eligible due to missing "+prereqs[j].getConfigId());
-                eligible = false;
-            }
-        }
-        String[] gerVersions = getChildrenText(config, "geronimo-version");
-        if(gerVersions.length > 0) {
-            String version = serverInfo.getVersion();
-            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;
-                }
-            }
-            if(!match) eligible = false;
-        }
-        String[] jvmVersions = getChildrenText(config, "jvm-version");
-        if(jvmVersions.length > 0) {
-            String version = System.getProperty("java.version");
-            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;
-                }
-            }
-            if(!match) eligible = false;
-        }
-        Artifact artifact = null;
-        boolean installed = false;
-        if (configId != null) {
-            artifact = Artifact.create(configId);
-            // Tests, etc. don't need to have a ConfigurationManager
-            installed = configManager != null && 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);
-        data.setGeronimoVersions(gerVersions);
-        data.setJvmVersions(jvmVersions);
-        data.setLicenses(licenses);
-        data.setPrerequisites(prereqs);
-        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;
-    }
-
-    private static String getChildText(Element root, String property) {
-        NodeList children = root.getChildNodes();
-        for(int i=0; i<children.getLength(); i++) {
-            Node check = children.item(i);
-            if(check.getNodeType() == Node.ELEMENT_NODE && check.getNodeName().equals(property)) {
-                return getText(check);
-            }
-        }
-        return null;
-    }
-
-    private static String getText(Node target) {
-        NodeList nodes = target.getChildNodes();
-        StringBuffer buf = null;
-        for(int j=0; j<nodes.getLength(); j++) {
-            Node node = nodes.item(j);
-            if(node.getNodeType() == Node.TEXT_NODE) {
-                if(buf == null) {
-                    buf = new StringBuffer();
-                }
-                buf.append(node.getNodeValue());
-            }
-        }
-        return buf == null ? null : buf.toString();
-    }
-
-    private static String[] getChildrenText(Element root, String property) {
-        NodeList children = root.getChildNodes();
-        List results = new ArrayList();
-        for(int i=0; i<children.getLength(); i++) {
-            Node check = children.item(i);
-            if(check.getNodeType() == Node.ELEMENT_NODE && check.getNodeName().equals(property)) {
-                NodeList nodes = check.getChildNodes();
-                StringBuffer buf = null;
-                for(int j=0; j<nodes.getLength(); j++) {
-                    Node node = nodes.item(j);
-                    if(node.getNodeType() == Node.TEXT_NODE) {
-                        if(buf == null) {
-                            buf = new StringBuffer();
-                        }
-                        buf.append(node.getNodeValue());
-                    }
-                }
-                results.add(buf == null ? null : buf.toString());
-            }
-        }
-        return (String[]) results.toArray(new String[results.size()]);
-    }
-
-
-    public DownloadResults install(ConfigurationList list, String username, String password) {
+    /**
+     * Installs a configuration from a remote repository into the local Geronimo server,
+     * including all its dependencies.  The caller will get the results when the
+     * operation completes.  Note that this method does not throw exceptions on failure,
+     * but instead sets the failure property of the DownloadResults.
+     *
+     * @param username         Optional username, if the maven repo uses HTTP Basic authentication.
+     *                         Set this to null if no authentication is required.
+     * @param password         Optional password, if the maven repo uses HTTP Basic authentication.
+     *                         Set this to null if no authentication is required.
+     * @param pluginsToInstall The list of configurations to install
+     */
+    public DownloadResults install(PluginList pluginsToInstall, String username, String password) {
         DownloadResults results = new DownloadResults();
-        install(list, username, password, results);
+        install(pluginsToInstall, username, password, results);
         return results;
     }
 
-    public void install(ConfigurationList list, String username, String password, DownloadPoller poller) {
+    /**
+     * Installs a configuration from a remote repository into the local Geronimo server,
+     * including all its dependencies.  The method blocks until the operation completes,
+     * but the caller will be notified of progress frequently along the way (using the
+     * supplied DownloadPoller).  Therefore the caller is meant to create the poller and
+     * then call this method in a background thread.  Note that this method does not
+     * throw exceptions on failure, but instead sets the failure property of the
+     * DownloadPoller.
+     *
+     * @param pluginsToInstall The list of configurations to install
+     * @param username         Optional username, if the maven repo uses HTTP Basic authentication.
+     *                         Set this to null if no authentication is required.
+     * @param password         Optional password, if the maven repo uses HTTP Basic authentication.
+     *                         Set this to null if no authentication is required.
+     * @param poller           Will be notified with status updates as the download proceeds
+     */
+    public void install(PluginList pluginsToInstall, String username, String password, DownloadPoller poller) {
         try {
+            Map metaMap = new HashMap();
             // Step 1: validate everything
-            for (int i = 0; i < list.getConfigurations().length; i++) {
-                ConfigurationMetadata metadata = list.getConfigurations()[i];
-                validateConfiguration(metadata);
+            for (int i = 0; i < pluginsToInstall.getPlugins().length; i++) {
+                PluginMetadata metadata = pluginsToInstall.getPlugins()[i];
+                validatePlugin(metadata);
+                metaMap.put(metadata.getModuleId(), metadata);
             }
 
             // Step 2: everything is valid, do the installation
-            for (int i = 0; i < list.getConfigurations().length; i++) {
+            for (int i = 0; i < pluginsToInstall.getPlugins().length; i++) {
                 // 1. Identify the configuration
-                ConfigurationMetadata metadata = list.getConfigurations()[i];
+                PluginMetadata metadata = pluginsToInstall.getPlugins()[i];
                 // 2. Unload obsoleted configurations
                 List obsoletes = new ArrayList();
                 for (int j = 0; j < metadata.getObsoletes().length; j++) {
@@ -506,8 +348,12 @@
                 }
                 // 3. Download the artifact if necessary, and its dependencies
                 Set working = new HashSet();
-                if(metadata.getConfigId() != null) {
-                    downloadArtifact(metadata.getConfigId(), list.getMainRepository(), list.getBackupRepositories(),
+                if(metadata.getModuleId() != null) {
+                    URL[] repos = pluginsToInstall.getRepositories();
+                    if(metadata.getRepositories().length > 0) {
+                        repos = metadata.getRepositories();
+                    }
+                    downloadArtifact(metadata.getModuleId(), metaMap, repos,
                             username, password, new ResultsFileWriteMonitor(poller), working);
                 } else {
                     String[] deps = metadata.getDependencies();
@@ -517,9 +363,13 @@
                         if(configManager.isRunning(entry)) {
                             continue;
                         }
-                        downloadArtifact(entry, list.getMainRepository(), list.getBackupRepositories(),
+                        URL[] repos = pluginsToInstall.getRepositories();
+                        if(metadata.getRepositories().length > 0) {
+                            repos = metadata.getRepositories();
+                        }
+                        downloadArtifact(entry, metaMap, repos,
                                 username, password, new ResultsFileWriteMonitor(poller), working);
-                        poller.addInstalledConfigID(metadata.getConfigId());
+                        poller.addInstalledConfigID(metadata.getModuleId());
                     }
                 }
                 // 4. Uninstall obsolete configurations
@@ -528,14 +378,14 @@
                     configManager.uninstallConfiguration(artifact);
                 }
                 // 5. Installation of this configuration finished successfully
-                if(metadata.getConfigId() != null) {
-                    poller.addInstalledConfigID(metadata.getConfigId());
+                if(metadata.getModuleId() != null) {
+                    poller.addInstalledConfigID(metadata.getModuleId());
                 }
             }
 
             // Step 3: Start anything that's marked accordingly
-            for (int i = 0; i < list.getConfigurations().length; i++) {
-                ConfigurationMetadata metadata = list.getConfigurations()[i];
+            for (int i = 0; i < pluginsToInstall.getPlugins().length; i++) {
+                PluginMetadata metadata = pluginsToInstall.getPlugins()[i];
                 for (int j = 0; j < metadata.getForceStart().length; j++) {
                     String id = metadata.getForceStart()[j];
                     Artifact artifact = Artifact.create(id);
@@ -555,67 +405,26 @@
     }
 
     /**
-     * Installs from a pre-downloaded CAR file
+     * Installs a configuration from a remote repository into the local Geronimo server,
+     * including all its dependencies.  The method returns immediately, providing a key
+     * that can be used to poll the status of the download operation.  Note that the
+     * installation does not throw exceptions on failure, but instead sets the failure
+     * property of the DownloadResults that the caller can poll for.
+     *
+     * @param pluginsToInstall The list of configurations to install
+     * @param username         Optional username, if the maven repo uses HTTP Basic authentication.
+     *                         Set this to null if no authentication is required.
+     * @param password         Optional password, if the maven repo uses HTTP Basic authentication.
+     *                         Set this to null if no authentication is required.
+     *
+     * @return A key that can be passed to checkOnInstall
      */
-    public void install(File carFile, String username, String password, DownloadPoller poller) {
-        try {
-            // 1. Extract the configuration metadata
-            ConfigurationArchiveData data = loadConfigurationArchive(carFile);
-            if(data == null) {
-                throw new IllegalArgumentException("Invalid Configuration Archive "+carFile.getAbsolutePath()+" see server log for details");
-            }
-
-            // 2. Validate that we can install this
-            validateConfiguration(data.getConfiguration());
-
-            // 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);
-            }
-
-            // 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 {
-            poller.setFinished();
-        }
-    }
-
-    private void validateConfiguration(ConfigurationMetadata metadata) throws MissingDependencyException {
-        // 1. Check that it's not 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();
-        for (int i = 0; i < prereqs.length; i++) {
-            ConfigurationMetadata.Prerequisite prereq = prereqs[i];
-            if(resolver.queryArtifacts(prereq.getConfigId()).length == 0) {
-                throw new MissingDependencyException("Required configuration '"+prereq.getConfigId()+"' is not installed.");
-            }
-        }
-    }
-
-    public Object startInstall(final ConfigurationList configsToInstall, final String username, final String password) {
+    public Object startInstall(final PluginList pluginsToInstall, final String username, final String password) {
         Object key = getNextKey();
         final DownloadResults results = new DownloadResults();
         Runnable work = new Runnable() {
             public void run() {
-                install(configsToInstall, username, password, results);
+                install(pluginsToInstall, username, password, results);
             }
         };
         asyncKeys.put(key, results);
@@ -627,6 +436,26 @@
         return key;
     }
 
+    /**
+     * Installs a configuration downloaded from a remote repository into the local Geronimo
+     * server, including all its dependencies.  The method returns immediately, providing a
+     * key that can be used to poll the status of the download operation.  Note that the
+     * installation does not throw exceptions on failure, but instead sets the failure
+     * property of the DownloadResults that the caller can poll for.
+     *
+     * @param carFile   A CAR file downloaded from a remote repository.  This is a packaged
+     *                  configuration with included configuration information, but it may
+     *                  still have external dependencies that need to be downloaded
+     *                  separately.  The metadata in the CAR file includes a repository URL
+     *                  for these downloads, and the username and password arguments are
+     *                  used in conjunction with that.
+     * @param username  Optional username, if the maven repo uses HTTP Basic authentication.
+     *                  Set this to null if no authentication is required.
+     * @param password  Optional password, if the maven repo uses HTTP Basic authentication.
+     *                  Set this to null if no authentication is required.
+     *
+     * @return A key that can be passed to checkOnInstall
+     */
     public Object startInstall(final File carFile, final String username, final String password) {
         Object key = getNextKey();
         final DownloadResults results = new DownloadResults();
@@ -644,6 +473,14 @@
         return key;
     }
 
+    /**
+     * Gets the current progress of a download operation.  Note that once the
+     * DownloadResults is returned for this operation shows isFinished = true,
+     * the operation will be forgotten, so the caller should be careful not to
+     * call this again after the download has finished.
+     *
+     * @param key Identifies the operation to check on
+     */
     public DownloadResults checkOnInstall(Object key) {
         DownloadResults results = (DownloadResults) asyncKeys.get(key);
         results = results.duplicate();
@@ -654,38 +491,128 @@
     }
 
     /**
-     * Download (if necessary) and install something, which may be a Configuration or may
-     * be just a JAR.  For each artifact processed, all its dependencies will be
-     * processed as well.
-     *
-     * @param configID  Identifies the artifact to install
-     * @param repoURL   The main URL to contact the repository
-     * @param backups   Any additional repositories to search
-     * @param username  The username used for repositories secured with HTTP Basic authentication
-     * @param password  The password used for repositories secured with HTTP Basic authentication
-     * @param monitor   The ongoing results of the download operations, with some monitoring logic
-     *
-     * @throws IOException                 When there's a problem reading or writing data
-     * @throws FailedLoginException        When a repository requires authentication and either no username
-     *                                     and password are supplied or the username and password supplied
-     *                                     are not accepted
-     * @throws MissingDependencyException  When a dependency cannot be located in any of the listed repositories
+     * Installs from a pre-downloaded CAR file
      */
-    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?
-        Artifact[] matches = configManager.getArtifactResolver().queryArtifacts(configID);
-        if(matches.length == 0) {
-            OpenResult result = openStream(configID, repoURL, backups, username, password, monitor);
-            try {
-                writeableRepo.copyToRepository(result.getStream(), result.getFileSize(), result.getConfigID(), monitor); //todo: download SNAPSHOTS if previously available?
-                monitor.getResults().addDependencyInstalled(configID);
-                configID = result.getConfigID();
-            } finally {
+    public void install(File carFile, String username, String password, DownloadPoller poller) {
+        try {
+            // 1. Extract the configuration metadata
+            PluginMetadata data = loadCARFile(carFile, true);
+            if(data == null) {
+                throw new IllegalArgumentException("Invalid Configuration Archive "+carFile.getAbsolutePath()+" see server log for details");
+            }
+
+            // 2. Validate that we can install this
+            validatePlugin(data);
+
+            // 3. Install the CAR into the repository (it shouldn't be re-downloaded)
+            if(data.getModuleId() != null) {
+                ResultsFileWriteMonitor monitor = new ResultsFileWriteMonitor(poller);
+                writeableRepo.copyToRepository(carFile, data.getModuleId(), monitor);
+            }
+
+            // 4. Use the standard logic to remove obsoletes, install dependencies, etc.
+            //    This will validate all over again (oh, well)
+            install(new PluginList(data.getRepositories(), new PluginMetadata[]{data}),
+                    username, password, poller);
+        } catch (Exception e) {
+            poller.setFailure(e);
+        } finally {
+            poller.setFinished();
+        }
+    }
+
+    /**
+     * Ensures that a plugin is installable.
+     */
+    private void validatePlugin(PluginMetadata metadata) throws MissingDependencyException {
+        // 1. Check that it's not already running
+        if(metadata.getModuleId() != null) { // that is, it's a real configuration not a plugin list
+            if(configManager.isRunning(metadata.getModuleId())) {
+                throw new IllegalArgumentException("Configuration "+metadata.getModuleId()+" 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
+        PluginMetadata.Prerequisite[] prereqs = metadata.getPrerequisites();
+        for (int i = 0; i < prereqs.length; i++) {
+            PluginMetadata.Prerequisite prereq = prereqs[i];
+            if(resolver.queryArtifacts(prereq.getModuleId()).length == 0) {
+                throw new MissingDependencyException("Required configuration '"+prereq.getModuleId()+"' is not installed.");
+            }
+        }
+        // 3. Check that we meet the Geronimo, JVM versions
+        if(metadata.getGeronimoVersions().length > 0 && !checkGeronimoVersions(metadata.getGeronimoVersions())) {
+            throw new MissingDependencyException("Cannot install plugin "+metadata.getModuleId()+" on Geronimo "+serverInfo.getVersion());
+        }
+        if(metadata.getJvmVersions().length > 0 && !checkJVMVersions(metadata.getJvmVersions())) {
+            throw new MissingDependencyException("Cannot install plugin "+metadata.getModuleId()+" on JVM "+System.getProperty("java.version"));
+        }
+    }
+
+    /**
+     * Download (if necessary) and install something, which may be a Configuration or may
+     * be just a JAR.  For each artifact processed, all its dependencies will be
+     * processed as well.
+     *
+     * @param configID  Identifies the artifact to install
+     * @param repos     The URLs to contact the repositories (in order of preference)
+     * @param username  The username used for repositories secured with HTTP Basic authentication
+     * @param password  The password used for repositories secured with HTTP Basic authentication
+     * @param monitor   The ongoing results of the download operations, with some monitoring logic
+     *
+     * @throws IOException                 When there's a problem reading or writing data
+     * @throws FailedLoginException        When a repository requires authentication and either no username
+     *                                     and password are supplied or the username and password supplied
+     *                                     are not accepted
+     * @throws MissingDependencyException  When a dependency cannot be located in any of the listed repositories
+     */
+    private void downloadArtifact(Artifact configID, Map metadata, URL[] repos, 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?
+        Artifact[] matches = configManager.getArtifactResolver().queryArtifacts(configID);
+        if(matches.length == 0) { // not present, needs to be downloaded
+            OpenResult result = openStream(configID, repos, username, password, monitor);
+            try {
+                File tempFile = downloadFile(result, monitor);
+                PluginMetadata pluginData = ((PluginMetadata) metadata.get(configID));
+                // Only bother with the hash if we got it from a source other than the download file itself
+                PluginMetadata.Hash hash = pluginData == null ? null : pluginData.getHash();
+                if(hash != null) {
+                    String actual = ConfigurationStoreUtil.getActualChecksum(tempFile, hash.getType());
+                    if(!actual.equals(hash.getValue())) {
+                        throw new IOException("File download incorrect (expected "+hash.getType()+" hash "+hash.getValue()+" but got "+actual+")");
+                    }
+                }
+                // See if the download file has plugin metadata
+                if(pluginData == null) {
+                    try {
+                        pluginData = loadCARFile(tempFile, false);
+                    } catch (Exception e) {
+                        throw new IOException("Unable to read plugin metadata: "+e.getMessage());
+                    }
+                }
+                if(pluginData != null) { // it's a plugin, not a plain JAR
+                    validatePlugin(pluginData);
+                }
+                writeableRepo.copyToRepository(tempFile, result.getConfigID(), monitor); //todo: download SNAPSHOTS if previously available?
+                if(!tempFile.delete()) {
+                    log.warn("Unable to delete temporary download file "+tempFile.getAbsolutePath());
+                    tempFile.deleteOnExit();
+                }
+                monitor.getResults().addDependencyInstalled(configID);
+                configID = result.getConfigID();
+            } finally {
                 result.getStream().close();
             }
         } else {
@@ -716,7 +643,7 @@
             for (int i = 0; i < dependencies.length; i++) {
                 Dependency dep = dependencies[i];
                 Artifact artifact = dep.getArtifact();
-                downloadArtifact(artifact, repoURL, backups, username, password, monitor, soFar);
+                downloadArtifact(artifact, metadata, repos, username, password, monitor, soFar);
             }
         } catch (NoSuchConfigException e) {
             throw new IllegalStateException("Installed configuration into repository but ConfigStore does not see it: "+e.getMessage());
@@ -726,6 +653,30 @@
     }
 
     /**
+     * Downloads to a temporary file so we can validate the download before
+     * installing into the repository.
+     */
+    private File downloadFile(OpenResult result, ResultsFileWriteMonitor monitor) throws IOException {
+        InputStream in = result.getStream();
+        if(in == null) {
+            throw new IllegalStateException();
+        }
+        monitor.writeStarted(result.getConfigID().toString(), result.fileSize);
+        File file = File.createTempFile("geronimo-plugin-download-", ".tmp");
+        FileOutputStream out = new FileOutputStream(file);
+        byte[] buf = new byte[4096];
+        int count, total = 0;
+        while((count = in.read(buf)) > -1) {
+            out.write(buf, 0, count);
+            monitor.writeProgress(total += count);
+        }
+        monitor.writeComplete(total);
+        in.close();
+        out.close();
+        return file;
+    }
+
+    /**
      * Used to get dependencies for a JAR
      */
     private static Dependency[] getDependencies(Repository repo, Artifact artifact) {
@@ -752,23 +703,43 @@
         return (Dependency[]) dependencies.toArray(new Dependency[dependencies.size()]);
     }
 
+    /**
+     * Constructs a URL to a particular artifact in a particular repository
+     */
     private static URL getURL(Artifact configId, URL repository) throws MalformedURLException {
         return new URL(repository, configId.getGroupId().replace('.','/')+"/"+configId.getArtifactId()+"/"+configId.getVersion()+"/"+configId.getArtifactId()+"-"+configId.getVersion()+"."+configId.getType());
     }
 
-    private static OpenResult openStream(Artifact artifact, URL repo, URL[] backups, String username, String password, ResultsFileWriteMonitor monitor) throws IOException, FailedLoginException, MissingDependencyException {
+    /**
+     * Attemps to open a stream to an artifact in one of the listed repositories.
+     * The username and password provided are only used if one of the repositories
+     * returns an HTTP authentication failure on the first try.
+     *
+     * @param artifact  The artifact we're looking for, or null to just connect to the base repo URL
+     * @param repos     The base URLs to the repositories to search for the artifact
+     * @param username  A username if one of the repositories might require authentication
+     * @param password  A password if one of the repositories might require authentication
+     * @param monitor   Callback for progress on the connection operation
+     *
+     * @throws IOException Occurs when the IO with the repository failed
+     * @throws FailedLoginException Occurs when a repository requires authentication and either
+     *                              no username and password were provided or they weren't
+     *                              accepted
+     * @throws MissingDependencyException Occurs when none of the repositories has the artifact
+     *                                    in question
+     */
+    private static OpenResult openStream(Artifact artifact, URL[] repos, 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
         }
         if(artifact != null && !artifact.isResolved()) {
-            artifact = findArtifact(artifact, repo, backups, username, password, monitor);
+            artifact = findArtifact(artifact, repos, username, password, monitor);
         }
         InputStream in;
         LinkedList list = new LinkedList();
-        list.add(repo);
-        list.addAll(Arrays.asList(backups));
+        list.addAll(Arrays.asList(repos));
         while (true) {
             if(list.isEmpty()) {
                 throw new MissingDependencyException("Unable to download dependency "+artifact);
@@ -786,9 +757,17 @@
         }
     }
 
+    /**
+     * Does the meat of connecting to a URL
+     */
     private static InputStream connect(URL url, String username, String password, ResultsFileWriteMonitor monitor) throws IOException, FailedLoginException {
         return connect(url, username, password, monitor, null);
     }
+
+    /**
+     * Does the meat of connecting to a URL.  Can be used to just test the existance of
+     * something at the specified URL by passing the method 'HEAD'.
+     */
     private static InputStream connect(URL url, String username, String password, ResultsFileWriteMonitor monitor, String method) throws IOException, FailedLoginException {
         URLConnection con = url.openConnection();
         if(con instanceof HttpURLConnection) {
@@ -850,14 +829,17 @@
         }
     }
 
-    private static Artifact findArtifact(Artifact query, URL repo, URL[] backups, String username, String password, ResultsFileWriteMonitor monitor) throws MissingDependencyException {
+    /**
+     * Searches for an artifact in the listed repositories, where the artifact
+     * may have wildcards in the ID.
+     */
+    private static Artifact findArtifact(Artifact query, URL[] repos, String username, String password, ResultsFileWriteMonitor monitor) throws MissingDependencyException {
         if(query.getGroupId() == null || query.getArtifactId() == null || query.getType() == null) {
             throw new MissingDependencyException("No support yet for dependencies missing more than a version: "+query);
         }
         List list = new ArrayList();
-        list.add(repo);
-        for (int i = 0; i < backups.length; i++) {
-            list.add(backups[i]);
+        for (int i = 0; i < repos.length; i++) {
+            list.add(repos[i]);
         }
         Artifact result = null;
         for (int i = 0; i < list.size(); i++) {
@@ -874,6 +856,10 @@
         throw new MissingDependencyException("No repository has a valid artifact for "+query);
     }
 
+    /**
+     * Checks for an artifact in a specific repository, where the artifact may
+     * have wildcards in the ID.
+     */
     private static Artifact findArtifact(Artifact query, URL url, String username, String password, ResultsFileWriteMonitor monitor) throws IOException, FailedLoginException, ParserConfigurationException, SAXException {
         monitor.getResults().setCurrentMessage("Searching for "+query+" at "+url);
         String base = query.getGroupId().replace('.', '/') + "/" + query.getArtifactId();
@@ -883,8 +869,8 @@
         if(in == null) {
             return null;
         }
-        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-        DocumentBuilder builder = factory.newDocumentBuilder();
+        // Don't use the validating parser that we normally do
+        DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
         Document doc = builder.parse(in);
         Element root = doc.getDocumentElement();
         NodeList list = root.getElementsByTagName("versions");
@@ -911,50 +897,13 @@
         return null;
     }
 
-    private static class ResultsFileWriteMonitor implements FileWriteMonitor {
-        private final DownloadPoller results;
-        private int totalBytes;
-        private String file;
-
-        public ResultsFileWriteMonitor(DownloadPoller results) {
-            this.results = results;
-        }
-
-        public void setTotalBytes(int totalBytes) {
-            this.totalBytes = totalBytes;
-        }
-
-        public int getTotalBytes() {
-            return totalBytes;
-        }
-
-        public void writeStarted(String fileDescription, int fileSize) {
-            totalBytes = fileSize;
-            file = fileDescription;
-            results.setCurrentFile(fileDescription);
-            results.setCurrentMessage("Downloading "+fileDescription+"...");
-            results.setCurrentFilePercent(totalBytes > 0 ? 0 : -1);
-        }
-
-        public void writeProgress(int bytes) {
-            if(totalBytes > 0) {
-                results.setCurrentFilePercent((bytes*100)/totalBytes);
-            } else {
-                results.setCurrentMessage((bytes/1024)+" kB of "+file);
-            }
-        }
-
-        public void writeComplete(int bytes) {
-            results.setCurrentFilePercent(100);
-            results.setCurrentMessage("Downloaded "+file+" ("+(bytes/1024)+" kB)");
-            results.addDownloadBytes(bytes);
-        }
-
-        public DownloadPoller getResults() {
-            return results;
-        }
-    }
-
+    /**
+     * Puts the name and ID of a plugin into the argument map of plugins,
+     * by reading the values out of the provided plugin descriptor file.
+     *
+     * @param xml     The geronimo-plugin.xml for this plugin
+     * @param plugins The result map to populate
+     */
     private void readNameAndID(File xml, Map plugins) {
         try {
             SAXParserFactory factory = SAXParserFactory.newInstance();
@@ -969,6 +918,13 @@
         }
     }
 
+    /**
+     * Puts the name and ID of a plugin into the argument map of plugins,
+     * by reading the values out of the provided plugin descriptor stream.
+     *
+     * @param xml     The geronimo-plugin.xml for this plugin
+     * @param plugins The result map to populate
+     */
     private void readNameAndID(InputStream xml, Map plugins) {
         try {
             SAXParserFactory factory = SAXParserFactory.newInstance();
@@ -983,87 +939,377 @@
         }
     }
 
-    private static class PluginNameIDHandler extends DefaultHandler {
-        private String id = "";
-        private String name = "";
-        private String element = null;
+    /**
+     * Replaces all the dependency elements in the argument configuration data
+     * with the dependencies from the actual data for that module.
+     */
+    private void overrideDependencies(ConfigurationData data, PluginMetadata metadata) {
+        //todo: this ends up doing a little more work than necessary
+        PluginMetadata temp = createDefaultMetadata(data);
+        metadata.setDependencies(temp.getDependencies());
+    }
 
-        public void characters(char ch[], int start, int length) throws SAXException {
-            if(element != null) {
-                if(element.equals("config-id")) {
-                    id += new String(ch, start, length);
-                } else if(element.equals("name")) {
-                    name += new String(ch, start, length);
+    /**
+     * Generates a default plugin metadata based on the data for this module
+     * in the server.
+     */
+    private PluginMetadata createDefaultMetadata(ConfigurationData data) {
+        PluginMetadata meta = new PluginMetadata(data.getId().toString(), // name
+                data.getId(), // module ID
+                "Unknown", // category
+                "Please provide a description",
+                null, // URL
+                null, // author
+                null, // hash
+                true, // installed
+                false);
+        meta.setGeronimoVersions(new String[]{serverInfo.getVersion()});
+        meta.setJvmVersions(new String[0]);
+        meta.setLicenses(new PluginMetadata.License[0]);
+        meta.setObsoletes(new String[0]);
+        List deps = new ArrayList();
+        PluginMetadata.Prerequisite prereq = null;
+        prereq = processDependencyList(data.getEnvironment().getDependencies(), prereq, deps);
+        Map children = data.getChildConfigurations();
+        for (Iterator it = children.values().iterator(); it.hasNext();) {
+            ConfigurationData child = (ConfigurationData) it.next();
+            prereq = processDependencyList(child.getEnvironment().getDependencies(), prereq, deps);
+        }
+        meta.setDependencies((String[]) deps.toArray(new String[deps.size()]));
+        meta.setPrerequisites(prereq == null ? new PluginMetadata.Prerequisite[0] : new PluginMetadata.Prerequisite[]{prereq});
+        return meta;
+    }
+
+    /**
+     * Read the plugin metadata out of a plugin CAR file on disk.
+     */
+    private PluginMetadata loadCARFile(File file, boolean definitelyCAR) throws IOException, ParserConfigurationException, SAXException {
+        if(!file.canRead()) {
+            log.error("Cannot read from downloaded CAR file "+file.getAbsolutePath());
+            return null;
+        }
+        JarFile jar = new JarFile(file);
+        Document doc;
+        try {
+            JarEntry entry = jar.getJarEntry("META-INF/geronimo-plugin.xml");
+            if(entry == null) {
+                if(definitelyCAR) {
+                    log.error("Downloaded CAR file does not contain META-INF/geronimo-plugin.xml file");
                 }
+                jar.close();
+                return null;
             }
+            InputStream in = jar.getInputStream(entry);
+            DocumentBuilder builder = createDocumentBuilder();
+            doc = builder.parse(in);
+            in.close();
+        } finally {
+            jar.close();
         }
+        return loadPluginMetadata(doc, file.getAbsolutePath());
+    }
 
-        public void endElement(String uri, String localName, String qName) throws SAXException {
-            element = null;
+    /**
+     * Read a set of plugin metadata from a DOM document.
+     */
+    private PluginMetadata loadPluginMetadata(Document doc, String file) throws SAXException, MalformedURLException {
+        Element root = doc.getDocumentElement();
+        if(!root.getNodeName().equals("geronimo-plugin")) {
+            log.error("Configuration archive "+file+" does not have a geronimo-plugin in META-INF/geronimo-plugin.xml");
+            return null;
         }
+        return processPlugin(root);
+    }
 
-        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
-            if(qName.equals("config-id") || qName.equals("name")) {
-                element = qName;
+    /**
+     * Loads the list of all available plugins from the specified stream
+     * (representing geronimo-plugins.xml at the specified repository).
+     */
+    private PluginList loadPluginList(URL repo, InputStream in) throws ParserConfigurationException, IOException, SAXException {
+        DocumentBuilder builder = createDocumentBuilder();
+        Document doc = builder.parse(in);
+        in.close();
+        Element root = doc.getDocumentElement(); // geronimo-plugin-list
+        NodeList configs = root.getElementsByTagName("plugin");
+        List results = new ArrayList();
+        for (int i = 0; i < configs.getLength(); i++) {
+            Element config = (Element) configs.item(i);
+            PluginMetadata data = processPlugin(config);
+            results.add(data);
+        }
+        String[] repos = getChildrenText(root, "default-repository");
+        URL[] repoURLs = new URL[repos.length];
+        for(int i = 0; i < repos.length; i++) {
+            if(repos[i].endsWith("/")) {
+                repoURLs[i] = new URL(repos[i]);
+            } else {
+                repoURLs[i] = new URL(repos[i]+"/");
             }
         }
 
-        public void endDocument() throws SAXException {
-            id = id.trim();
-            name = name.trim();
+        PluginMetadata[] data = (PluginMetadata[]) results.toArray(new PluginMetadata[results.size()]);
+        return new PluginList(repoURLs, data);
+    }
+
+    /**
+     * Common logic for setting up a document builder to deal with plugin files.
+     * @return
+     * @throws ParserConfigurationException
+     */
+    private static DocumentBuilder createDocumentBuilder() throws ParserConfigurationException {
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setValidating(true);
+        factory.setNamespaceAware(true);
+        factory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage",
+                             "http://www.w3.org/2001/XMLSchema");
+        factory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaSource",
+                             PluginInstallerGBean.class.getResourceAsStream("/META-INF/schema/plugins-1.1.xsd"));
+        DocumentBuilder builder = factory.newDocumentBuilder();
+        builder.setErrorHandler(new ErrorHandler() {
+            public void error(SAXParseException exception) throws SAXException {
+                throw new SAXException("Unable to read plugin file", exception);
+            }
+
+            public void fatalError(SAXParseException exception) throws SAXException {
+                throw new SAXException("Unable to read plugin file", exception);
+            }
+
+            public void warning(SAXParseException exception) {
+                log.warn("Warning reading XML document", exception);
+            }
+        });
+        return builder;
+    }
+
+    /**
+     * Given a DOM element representing a plugin, load it into a PluginMetadata
+     * object.
+     */
+    private PluginMetadata processPlugin(Element plugin) throws SAXException, MalformedURLException {
+        String moduleId = getChildText(plugin, "module-id");
+        NodeList licenseNodes = plugin.getElementsByTagName("license");
+        PluginMetadata.License[] licenses = new PluginMetadata.License[licenseNodes.getLength()];
+        for(int j=0; j<licenseNodes.getLength(); j++) {
+            Element node = (Element) licenseNodes.item(j);
+            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 PluginMetadata.License(licenseName, Boolean.valueOf(openSource).booleanValue());
+        }
+        PluginMetadata.Hash hash = null;
+        NodeList hashList = plugin.getElementsByTagName("hash");
+        if(hashList.getLength() > 0) {
+            Element elem = (Element) hashList.item(0);
+            hash = new PluginMetadata.Hash(elem.getAttribute("type"), getText(elem));
         }
+        boolean eligible = true;
+        NodeList preNodes = plugin.getElementsByTagName("prerequisite");
+        PluginMetadata.Prerequisite[] prereqs = new PluginMetadata.Prerequisite[preNodes.getLength()];
+        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 PluginMetadata.Prerequisite(artifact, present,
+                    getChildText(node, "resource-type"), getChildText(node, "description"));
+            if(!present) {
+                log.debug(moduleId+" is not eligible due to missing "+prereqs[j].getModuleId());
+                eligible = false;
+            }
+        }
+        String[] gerVersions = getChildrenText(plugin, "geronimo-version");
+        if(gerVersions.length > 0) {
+            boolean match = checkGeronimoVersions(gerVersions);
+            if(!match) eligible = false;
+        }
+        String[] jvmVersions = getChildrenText(plugin, "jvm-version");
+        if(jvmVersions.length > 0) {
+            boolean match = checkJVMVersions(jvmVersions);
+            if(!match) eligible = false;
+        }
+        String[] repoNames = getChildrenText(plugin, "source-repository");
+        URL[] repos = new URL[repoNames.length];
+        for (int i = 0; i < repos.length; i++) {
+            repos[i] = new URL(repoNames[i]);
+        }
+        Artifact artifact = null;
+        boolean installed = false;
+        if (moduleId != null) {
+            artifact = Artifact.create(moduleId);
+            // Tests, etc. don't need to have a ConfigurationManager
+            installed = configManager != null && configManager.isLoaded(artifact);
+        }
+        log.trace("Checking "+moduleId+": installed="+installed+", eligible="+eligible);
+        PluginMetadata data = new PluginMetadata(getChildText(plugin, "name"),
+                artifact,
+                getChildText(plugin, "category"),
+                getChildText(plugin, "description"),
+                getChildText(plugin, "url"),
+                getChildText(plugin, "author"),
+                hash,
+                installed, eligible);
+        data.setGeronimoVersions(gerVersions);
+        data.setJvmVersions(jvmVersions);
+        data.setLicenses(licenses);
+        data.setPrerequisites(prereqs);
+        data.setRepositories(repos);
+        NodeList list = plugin.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(plugin, "obsoletes"));
+        return data;
+    }
 
-        public String getID() {
-            return id;
+    /**
+     * Check whether the specified JVM versions match the current runtime
+     * environment.
+     *
+     * @return true if the specified versions match the current
+     *              execution environment as defined by plugins-1.1.xsd
+     */
+    private boolean checkJVMVersions(String[] jvmVersions) {
+        if(jvmVersions.length == 0) return true;
+        String version = System.getProperty("java.version");
+        boolean match = false;
+        for (int j = 0; j < jvmVersions.length; j++) {
+            String jvmVersion = jvmVersions[j];
+            if(jvmVersion == null || jvmVersion.equals("")) {
+                throw new IllegalStateException("jvm-version should not be empty!");
+            }
+            if(version.startsWith(jvmVersion)) {
+                match = true;
+                break;
+            }
         }
+        return match;
+    }
 
-        public String getName() {
-            return name;
+    /**
+     * Check whether the specified Geronimo versions match the current runtime
+     * environment.
+     *
+     * @return true if the specified versions match the current
+     *              execution environment as defined by plugins-1.1.xsd
+     */
+    private boolean checkGeronimoVersions(String[] gerVersions) {
+        if(gerVersions.length == 0) return true;
+        String version = serverInfo.getVersion();
+        boolean match = false;
+        for (int j = 0; j < gerVersions.length; j++) {
+            String gerVersion = gerVersions[j];
+            if(gerVersion == null || gerVersion.equals("")) {
+                throw new IllegalStateException("geronimo-version should not be empty!");
+            }
+            if(gerVersion.equals(version)) {
+                match = true;
+                break;
+            }
         }
+        return match;
+    }
 
-        public boolean isComplete() {
-            return !id.equals("") && !name.equals("");
+    /**
+     * Gets the text out of a child of the specified DOM element.
+     *
+     * @param root      The parent DOM element
+     * @param property  The name of the child element that holds the text
+     */
+    private static String getChildText(Element root, String property) {
+        NodeList children = root.getChildNodes();
+        for(int i=0; i<children.getLength(); i++) {
+            Node check = children.item(i);
+            if(check.getNodeType() == Node.ELEMENT_NODE && check.getNodeName().equals(property)) {
+                return getText(check);
+            }
         }
+        return null;
     }
 
-    private void overrideDependencies(ConfigurationData data, ConfigurationMetadata metadata) {
-        //todo: this ends up doing a little more work than necessary
-        ConfigurationMetadata temp = createDefaultMetadata(data);
-        metadata.setDependencies(temp.getDependencies());
+    /**
+     * Gets all the text contents of the specified DOM node.
+     */
+    private static String getText(Node target) {
+        NodeList nodes = target.getChildNodes();
+        StringBuffer buf = null;
+        for(int j=0; j<nodes.getLength(); j++) {
+            Node node = nodes.item(j);
+            if(node.getNodeType() == Node.TEXT_NODE) {
+                if(buf == null) {
+                    buf = new StringBuffer();
+                }
+                buf.append(node.getNodeValue());
+            }
+        }
+        return buf == null ? null : buf.toString();
     }
 
-    private ConfigurationMetadata createDefaultMetadata(ConfigurationData data) {
-        ConfigurationMetadata meta = new ConfigurationMetadata(data.getId(), data.getId().toString(),
-                "Please provide a description", "Unknown", true, false);
-        meta.setGeronimoVersions(new String[]{serverInfo.getVersion()});
-        meta.setJvmVersions(new String[0]);
-        meta.setLicenses(new ConfigurationMetadata.License[0]);
-        meta.setObsoletes(new String[0]);
-        List deps = new ArrayList();
-        ConfigurationMetadata.Prerequisite prereq = null;
-        prereq = processDependencyList(data.getEnvironment().getDependencies(), prereq, deps);
-        Map children = data.getChildConfigurations();
-        for (Iterator it = children.values().iterator(); it.hasNext();) {
-            ConfigurationData child = (ConfigurationData) it.next();
-            prereq = processDependencyList(child.getEnvironment().getDependencies(), prereq, deps);
+    /**
+     * Gets the text out of all the child nodes of a certain type.  The result
+     * array has one element for each child of the specified DOM element that
+     * has the specified name.
+     *
+     * @param root      The parent DOM element
+     * @param property  The name of the child elements that hold the text
+     */
+    private static String[] getChildrenText(Element root, String property) {
+        NodeList children = root.getChildNodes();
+        List results = new ArrayList();
+        for(int i=0; i<children.getLength(); i++) {
+            Node check = children.item(i);
+            if(check.getNodeType() == Node.ELEMENT_NODE && check.getNodeName().equals(property)) {
+                NodeList nodes = check.getChildNodes();
+                StringBuffer buf = null;
+                for(int j=0; j<nodes.getLength(); j++) {
+                    Node node = nodes.item(j);
+                    if(node.getNodeType() == Node.TEXT_NODE) {
+                        if(buf == null) {
+                            buf = new StringBuffer();
+                        }
+                        buf.append(node.getNodeValue());
+                    }
+                }
+                results.add(buf == null ? null : buf.toString());
+            }
         }
-        meta.setDependencies((String[]) deps.toArray(new String[deps.size()]));
-        meta.setPrerequisites(prereq == null ? new ConfigurationMetadata.Prerequisite[0] : new ConfigurationMetadata.Prerequisite[]{prereq});
-        return meta;
+        return (String[]) results.toArray(new String[results.size()]);
     }
 
-    private ConfigurationMetadata.Prerequisite processDependencyList(List real, ConfigurationMetadata.Prerequisite prereq, List deps) {
+    /**
+     * Generates dependencies and an optional prerequisite based on a list of
+     * dependencies for a Gernonimo module.
+     *
+     * @param real   A list with elements of type Dependency
+     * @param prereq The incoming prerequisite (if any), which may be replaced
+     * @param deps   A list with elements of type String (holding a module ID / Artifact name)
+     *
+     * @return The resulting prerequisite, if any.
+     */
+    private PluginMetadata.Prerequisite processDependencyList(List real, PluginMetadata.Prerequisite prereq, List deps) {
         for (int i = 0; i < real.size(); i++) {
             Dependency dep = (Dependency) real.get(i);
             if(dep.getArtifact().getGroupId().equals("geronimo")) {
                 if(dep.getArtifact().getArtifactId().indexOf("jetty") > -1) {
                     if(prereq == null) {
-                        prereq = new ConfigurationMetadata.Prerequisite(dep.getArtifact(), true, "Web Container", "This plugin works with the Geronimo/Jetty distribution.  It is not intended to run in the Geronimo/Tomcat distribution.  There is a separate version of this plugin that works with Tomcat.");
+                        prereq = new PluginMetadata.Prerequisite(dep.getArtifact(), true, "Web Container", "This plugin works with the Geronimo/Jetty distribution.  It is not intended to run in the Geronimo/Tomcat distribution.  There is a separate version of this plugin that works with Tomcat.");
                     }
                     continue;
                 } else if(dep.getArtifact().getArtifactId().indexOf("tomcat") > -1) {
                     if(prereq == null) {
-                        prereq = new ConfigurationMetadata.Prerequisite(dep.getArtifact(), true, "Web Container", "This plugin works with the Geronimo/Tomcat distribution.  It is not intended to run in the Geronimo/Jetty distribution.  There is a separate version of this plugin that works with Jetty.");
+                        prereq = new PluginMetadata.Prerequisite(dep.getArtifact(), true, "Web Container", "This plugin works with the Geronimo/Tomcat distribution.  It is not intended to run in the Geronimo/Jetty distribution.  There is a separate version of this plugin that works with Jetty.");
                     }
                     continue;
                 }
@@ -1075,27 +1321,39 @@
         return prereq;
     }
 
-    private static Document writeConfigurationMetadata(ConfigurationArchiveData metadata) throws ParserConfigurationException {
-        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-        DocumentBuilder builder = factory.newDocumentBuilder();
+    /**
+     * Writes plugin metadata to a DOM tree.
+     */
+    private static Document writePluginMetadata(PluginMetadata data) throws ParserConfigurationException {
+        DocumentBuilder builder = createDocumentBuilder();
         Document doc = builder.newDocument();
-        Element root = doc.createElement("geronimo-plugin");
-        doc.appendChild(root);
-        Element config = doc.createElement("configuration");
-        root.appendChild(config);
+        Element config = doc.createElementNS("http://geronimo.apache.org/xml/ns/plugins-1.1", "geronimo-plugin");
+        config.setAttribute("xmlns", "http://geronimo.apache.org/xml/ns/plugins-1.1");
+        doc.appendChild(config);
 
-        ConfigurationMetadata data = metadata.getConfiguration();
         addTextChild(doc, config, "name", data.getName());
-        addTextChild(doc, config, "config-id", data.getConfigId().toString());
+        addTextChild(doc, config, "config-id", data.getModuleId().toString());
         addTextChild(doc, config, "category", data.getCategory());
         addTextChild(doc, config, "description", data.getDescription());
+        if(data.getPluginURL() != null) {
+            addTextChild(doc, config, "url", data.getPluginURL());
+        }
+        if(data.getAuthor() != null) {
+            addTextChild(doc, config, "author", data.getAuthor());
+        }
         for (int i = 0; i < data.getLicenses().length; i++) {
-            ConfigurationMetadata.License license = data.getLicenses()[i];
+            PluginMetadata.License license = data.getLicenses()[i];
             Element lic = doc.createElement("license");
             lic.appendChild(doc.createTextNode(license.getName()));
             lic.setAttribute("osi-approved", Boolean.toString(license.isOsiApproved()));
             config.appendChild(lic);
         }
+        if(data.getHash() != null) {
+            Element hash = doc.createElement("hash");
+            hash.setAttribute("type", data.getHash().getType());
+            hash.appendChild(doc.createTextNode(data.getHash().getValue()));
+            config.appendChild(hash);
+        }
         for (int i = 0; i < data.getGeronimoVersions().length; i++) {
             addTextChild(doc, config, "geronimo-version", data.getGeronimoVersions()[i]);
         }
@@ -1103,9 +1361,9 @@
             addTextChild(doc, config, "jvm-version", data.getJvmVersions()[i]);
         }
         for (int i = 0; i < data.getPrerequisites().length; i++) {
-            ConfigurationMetadata.Prerequisite prereq = data.getPrerequisites()[i];
+            PluginMetadata.Prerequisite prereq = data.getPrerequisites()[i];
             Element pre = doc.createElement("prerequisite");
-            addTextChild(doc, pre, "id", prereq.getConfigId().toString());
+            addTextChild(doc, pre, "id", prereq.getModuleId().toString());
             if(prereq.getResourceType() != null) {
                 addTextChild(doc, pre, "resource-type", prereq.getResourceType());
             }
@@ -1120,25 +1378,134 @@
         for (int i = 0; i < data.getObsoletes().length; i++) {
             addTextChild(doc, config, "obsoletes", data.getObsoletes()[i]);
         }
-
-        Element repo = doc.createElement("source-repository");
-        repo.appendChild(doc.createTextNode(metadata.getRepository() == null ? "" : metadata.getRepository().toString()));
-        root.appendChild(repo);
-        for (int i = 0; i < metadata.getBackups().length; i++) {
-            URL url = metadata.getBackups()[i];
-            Element backup = doc.createElement("backup-repository");
-            backup.appendChild(doc.createTextNode(url.toString()));
-            root.appendChild(backup);
+        for (int i = 0; i < data.getRepositories().length; i++) {
+            URL url = data.getRepositories()[i];
+            addTextChild(doc, config, "source-repository", url.toString());
         }
         return doc;
     }
 
+    /**
+     * Adds a child of the specified Element that just has the specified text content
+     * @param doc     The document
+     * @param parent  The parent element
+     * @param name    The name of the child element to add
+     * @param text    The contents of the child element to add
+     */
     private static void addTextChild(Document doc, Element parent, String name, String text) {
         Element child = doc.createElement(name);
         child.appendChild(doc.createTextNode(text));
         parent.appendChild(child);
     }
 
+    /**
+     * Gets a token unique to this run of the server, used to track asynchronous
+     * downloads.
+     */
+    private static Object getNextKey() {
+        int value;
+        synchronized(PluginInstallerGBean.class) {
+            value = ++counter;
+        }
+        return new Integer(value);
+    }
+
+    /**
+     * Helper clas to extract a name and module ID from a plugin metadata file.
+     */
+    private static class PluginNameIDHandler extends DefaultHandler {
+        private String id = "";
+        private String name = "";
+        private String element = null;
+
+        public void characters(char ch[], int start, int length) throws SAXException {
+            if(element != null) {
+                if(element.equals("module-id")) {
+                    id += new String(ch, start, length);
+                } else if(element.equals("name")) {
+                    name += new String(ch, start, length);
+                }
+            }
+        }
+
+        public void endElement(String uri, String localName, String qName) throws SAXException {
+            element = null;
+        }
+
+        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+            if(qName.equals("module-id") || qName.equals("name")) {
+                element = qName;
+            }
+        }
+
+        public void endDocument() throws SAXException {
+            id = id.trim();
+            name = name.trim();
+        }
+
+        public String getID() {
+            return id;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public boolean isComplete() {
+            return !id.equals("") && !name.equals("");
+        }
+    }
+
+    /**
+     * Helper class to bridge a FileWriteMonitor to a DownloadPoller.
+     */
+    private static class ResultsFileWriteMonitor implements FileWriteMonitor {
+        private final DownloadPoller results;
+        private int totalBytes;
+        private String file;
+
+        public ResultsFileWriteMonitor(DownloadPoller results) {
+            this.results = results;
+        }
+
+        public void setTotalBytes(int totalBytes) {
+            this.totalBytes = totalBytes;
+        }
+
+        public int getTotalBytes() {
+            return totalBytes;
+        }
+
+        public void writeStarted(String fileDescription, int fileSize) {
+            totalBytes = fileSize;
+            file = fileDescription;
+            results.setCurrentFile(fileDescription);
+            results.setCurrentMessage("Downloading "+fileDescription+"...");
+            results.setCurrentFilePercent(totalBytes > 0 ? 0 : -1);
+        }
+
+        public void writeProgress(int bytes) {
+            if(totalBytes > 0) {
+                results.setCurrentFilePercent((bytes*100)/totalBytes);
+            } else {
+                results.setCurrentMessage((bytes/1024)+" kB of "+file);
+            }
+        }
+
+        public void writeComplete(int bytes) {
+            results.setCurrentFilePercent(100);
+            results.setCurrentMessage("Downloaded "+file+" ("+(bytes/1024)+" kB)");
+            results.addDownloadBytes(bytes);
+        }
+
+        public DownloadPoller getResults() {
+            return results;
+        }
+    }
+
+    /**
+     * Interesting data resulting from opening a connection to a remote file.
+     */
     private static class OpenResult {
         private final InputStream stream;
         private final Artifact configID;
@@ -1166,13 +1533,13 @@
     public static final GBeanInfo GBEAN_INFO;
 
     static {
-        GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(ConfigInstallerGBean.class);
+        GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(PluginInstallerGBean.class);
         infoFactory.addReference("ConfigManager", ConfigurationManager.class, "ConfigurationManager");
         infoFactory.addReference("Repository", WritableListableRepository.class, "Repository");
         infoFactory.addReference("ConfigStore", ConfigurationStore.class, "ConfigurationStore");
         infoFactory.addReference("ServerInfo", ServerInfo.class, "GBean");
         infoFactory.addReference("ThreadPool", ThreadPool.class, "GBean");
-        infoFactory.addInterface(ConfigurationInstaller.class);
+        infoFactory.addInterface(PluginInstaller.class);
 
         infoFactory.setConstructor(new String[]{"ConfigManager", "Repository", "ConfigStore", "ServerInfo","ThreadPool"});
 



Mime
View raw message