001    /**
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    package org.apache.geronimo.system.plugin;
018    
019    import java.io.BufferedOutputStream;
020    import java.io.File;
021    import java.io.FileOutputStream;
022    import java.io.IOException;
023    import java.io.InputStream;
024    import java.net.URI;
025    import java.net.URL;
026    import java.util.ArrayList;
027    import java.util.Collection;
028    import java.util.Collections;
029    import java.util.Comparator;
030    import java.util.Enumeration;
031    import java.util.HashMap;
032    import java.util.HashSet;
033    import java.util.Iterator;
034    import java.util.List;
035    import java.util.Map;
036    import java.util.Properties;
037    import java.util.Set;
038    import java.util.SortedSet;
039    import java.util.Stack;
040    import java.util.TreeSet;
041    import java.util.jar.JarEntry;
042    import java.util.jar.JarFile;
043    import java.util.jar.JarOutputStream;
044    import java.util.jar.Manifest;
045    import java.util.regex.Matcher;
046    import java.util.regex.Pattern;
047    import java.util.zip.ZipEntry;
048    
049    import javax.security.auth.login.FailedLoginException;
050    import javax.xml.parsers.SAXParser;
051    import javax.xml.parsers.SAXParserFactory;
052    
053    import org.apache.commons.logging.Log;
054    import org.apache.commons.logging.LogFactory;
055    import org.apache.geronimo.gbean.GBeanInfo;
056    import org.apache.geronimo.gbean.GBeanInfoBuilder;
057    import org.apache.geronimo.gbean.ReferenceCollection;
058    import org.apache.geronimo.gbean.ReferenceCollectionEvent;
059    import org.apache.geronimo.gbean.ReferenceCollectionListener;
060    import org.apache.geronimo.kernel.InvalidGBeanException;
061    import org.apache.geronimo.kernel.Kernel;
062    import org.apache.geronimo.kernel.basic.BasicKernel;
063    import org.apache.geronimo.kernel.config.ConfigurationData;
064    import org.apache.geronimo.kernel.config.ConfigurationManager;
065    import org.apache.geronimo.kernel.config.ConfigurationStore;
066    import org.apache.geronimo.kernel.config.InvalidConfigException;
067    import org.apache.geronimo.kernel.config.KernelConfigurationManager;
068    import org.apache.geronimo.kernel.config.NoSuchConfigException;
069    import org.apache.geronimo.kernel.config.NoSuchStoreException;
070    import org.apache.geronimo.kernel.config.PersistentConfigurationList;
071    import org.apache.geronimo.kernel.repository.Artifact;
072    import org.apache.geronimo.kernel.repository.ArtifactManager;
073    import org.apache.geronimo.kernel.repository.DefaultArtifactManager;
074    import org.apache.geronimo.kernel.repository.Dependency;
075    import org.apache.geronimo.kernel.repository.FileWriteMonitor;
076    import org.apache.geronimo.kernel.repository.ImportType;
077    import org.apache.geronimo.kernel.repository.MissingDependencyException;
078    import org.apache.geronimo.kernel.repository.Repository;
079    import org.apache.geronimo.kernel.repository.Version;
080    import org.apache.geronimo.kernel.repository.WritableListableRepository;
081    import org.apache.geronimo.kernel.util.XmlUtil;
082    import org.apache.geronimo.system.configuration.ConfigurationStoreUtil;
083    import org.apache.geronimo.system.configuration.RepositoryConfigurationStore;
084    import org.apache.geronimo.system.configuration.PluginAttributeStore;
085    import org.apache.geronimo.system.plugin.model.ArtifactType;
086    import org.apache.geronimo.system.plugin.model.ConfigXmlContentType;
087    import org.apache.geronimo.system.plugin.model.CopyFileType;
088    import org.apache.geronimo.system.plugin.model.DependencyType;
089    import org.apache.geronimo.system.plugin.model.HashType;
090    import org.apache.geronimo.system.plugin.model.LicenseType;
091    import org.apache.geronimo.system.plugin.model.PluginArtifactType;
092    import org.apache.geronimo.system.plugin.model.PluginListType;
093    import org.apache.geronimo.system.plugin.model.PluginType;
094    import org.apache.geronimo.system.plugin.model.PrerequisiteType;
095    import org.apache.geronimo.system.plugin.model.PropertyType;
096    import org.apache.geronimo.system.plugin.model.AttributesType;
097    import org.apache.geronimo.system.plugin.model.ModuleType;
098    import org.apache.geronimo.system.repository.Maven2Repository;
099    import org.apache.geronimo.system.serverinfo.BasicServerInfo;
100    import org.apache.geronimo.system.serverinfo.ServerInfo;
101    import org.apache.geronimo.system.threads.ThreadPool;
102    import org.codehaus.plexus.util.FileUtils;
103    import org.xml.sax.Attributes;
104    import org.xml.sax.SAXException;
105    import org.xml.sax.helpers.DefaultHandler;
106    
107    /**
108     * A GBean that knows how to download configurations from a Maven repository.
109     *
110     * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
111     */
112    public class PluginInstallerGBean implements PluginInstaller {
113        private final static Log log = LogFactory.getLog(PluginInstallerGBean.class);
114    
115    
116        private static int counter;
117        private final ConfigurationManager configManager;
118        private final GeronimoSourceRepository localSourceRepository;
119        private final WritableListableRepository writeableRepo;
120        private final ConfigurationStore configStore;
121        private final ServerInfo serverInfo;
122        private final Map<Object, DownloadResults> asyncKeys;
123        private final ThreadPool threadPool;
124        private final Collection<? extends ServerInstanceData> serverInstanceDatas;
125        private final ClassLoader classLoader;
126        private final Map<String, ServerInstance> servers = new HashMap<String, ServerInstance>();
127        private final Collection<PersistentConfigurationList> persistentConfigurationLists;
128    
129        // This regular expression for repository filename is taken from Maven1Repository.MAVEN_1_PATTERN
130        private static final Pattern MAVEN_1_PATTERN_PART = Pattern.compile("(.+)-([0-9].+)\\.([^0-9]+)");
131    
132        /**
133         * GBean constructor.  Supply an existing ConfigurationManager.  Use for adding to the current server.
134         *
135         * @param configManager                Configuration Manager for this server
136         * @param repository                   repository to install into
137         * @param configStore                  configuration store to install into
138         * @param serverInstanceDatas          set of server "layouts" to install config info into
139         * @param serverInfo                   location of server
140         * @param threadPool                   thread pool for async operations
141         * @param artifactManager              artifact manager to resolve existing artifacts
142         * @param persistentConfigurationLists used to start new plugins in a running server
143         * @param classLoader                  classLoader @throws IOException exception if server instance cannot be loaded
144         * @throws java.io.IOException from bad ServerInstance
145         */
146        public PluginInstallerGBean(ConfigurationManager configManager,
147                                    WritableListableRepository repository,
148                                    ConfigurationStore configStore,
149                                    Collection<? extends ServerInstanceData> serverInstanceDatas,
150                                    final ServerInfo serverInfo,
151                                    ThreadPool threadPool,
152                                    final ArtifactManager artifactManager,
153                                    Collection<PersistentConfigurationList> persistentConfigurationLists,
154                                    final ClassLoader classLoader) throws IOException {
155            this.writeableRepo = repository;
156            this.configStore = configStore;
157            this.serverInfo = serverInfo;
158            this.threadPool = threadPool;
159            asyncKeys = Collections.synchronizedMap(new HashMap<Object, DownloadResults>());
160            this.serverInstanceDatas = serverInstanceDatas;
161            this.persistentConfigurationLists = persistentConfigurationLists == null ? Collections.<PersistentConfigurationList>emptyList() : persistentConfigurationLists;
162            this.classLoader = classLoader;
163            if (configManager == null) {
164                throw new IllegalArgumentException("No default server instance set up");
165            }
166            this.configManager = configManager;
167            localSourceRepository = new GeronimoSourceRepository(configManager.getRepositories(), configManager.getArtifactResolver());
168            setUpServerInstances(serverInstanceDatas, serverInfo, artifactManager, servers, writeableRepo, true);
169        }
170    
171        /**
172         * Constructor for use in assembling a new server.
173         *
174         * @param serverInstanceDatas  set of server layouts
175         * @param kernel               kernel for current server
176         * @param classLoader          classLoader
177         * @param targetRepositoryPath location of repo to install into (not in current server)
178         * @param targetServerPath     location of server to install into (not current server
179         * @throws IOException if layouts can't be loaded
180         */
181        public PluginInstallerGBean(String targetRepositoryPath,
182                                    String targetServerPath,
183                                    Collection<? extends ServerInstanceData> serverInstanceDatas,
184                                    final Kernel kernel,
185                                    final ClassLoader classLoader) throws Exception {
186            final ArtifactManager artifactManager = new DefaultArtifactManager();
187    
188            FileUtils.forceMkdir(new File(targetServerPath));
189            serverInfo = new BasicServerInfo(targetServerPath, false);
190            File targetRepositoryFile = serverInfo.resolve(targetRepositoryPath);
191            FileUtils.forceMkdir(targetRepositoryFile);
192            writeableRepo = new Maven2Repository(targetRepositoryFile);
193            configStore = new RepositoryConfigurationStore(writeableRepo);
194            threadPool = null;
195            asyncKeys = Collections.synchronizedMap(new HashMap<Object, DownloadResults>());
196            this.serverInstanceDatas = serverInstanceDatas;
197            this.persistentConfigurationLists = Collections.emptyList();
198            this.classLoader = classLoader;
199            setUpServerInstances(serverInstanceDatas, serverInfo, artifactManager, servers, writeableRepo, false);
200            this.configManager = buildConfigurationManager(artifactManager, writeableRepo, kernel, configStore, classLoader, servers);
201            localSourceRepository = new GeronimoSourceRepository(configManager.getRepositories(), configManager.getArtifactResolver());
202        }
203    
204        private static void setUpServerInstances(Collection<? extends ServerInstanceData> serverInstanceDatas,
205                                                 final ServerInfo serverInfo, final ArtifactManager artifactManager,
206                                                 final Map<String, ServerInstance> servers,
207                                                 final WritableListableRepository writeableRepo,
208                                                 final boolean live) throws IOException {
209            List<ServerInstanceData> datas = new ArrayList<ServerInstanceData>(serverInstanceDatas);
210            boolean shrank = true;
211            while (shrank) {
212                shrank = false;
213                for (Iterator<ServerInstanceData> it = datas.iterator(); it.hasNext();) {
214                    ServerInstanceData instance = it.next();
215                    String dependsOn = instance.getAttributeManagerFrom();
216                    if (dependsOn == null || servers.containsKey(dependsOn)) {
217                        addServerInstance(instance, artifactManager, writeableRepo, serverInfo, servers, live);
218                        it.remove();
219                        shrank = true;
220                    }
221                }
222            }
223            if (!datas.isEmpty()) {
224                throw new IllegalStateException("Cannot resolve ServerInstanceDatas: " + datas);
225            }
226            if (serverInstanceDatas instanceof ReferenceCollection) {
227                ((ReferenceCollection) serverInstanceDatas).addReferenceCollectionListener(new ReferenceCollectionListener() {
228    
229                    public void memberAdded(ReferenceCollectionEvent event) {
230                        ServerInstanceData instance = (ServerInstanceData) event.getMember();
231                        try {
232                            addServerInstance(instance, artifactManager, writeableRepo, serverInfo, servers, live);
233                        } catch (IOException e) {
234                            //nothing to do?? log???
235                        }
236                    }
237    
238                    public void memberRemoved(ReferenceCollectionEvent event) {
239                        ServerInstanceData instance = (ServerInstanceData) event.getMember();
240                        servers.remove(instance.getName());
241                    }
242                });
243            }
244        }
245    
246        private static void addServerInstance(ServerInstanceData serverInstance,
247                                              ArtifactManager artifactManager,
248                                              WritableListableRepository targetRepo,
249                                              ServerInfo serverInfo,
250                                              Map<String, org.apache.geronimo.system.plugin.ServerInstance> servers,
251                                              boolean live) throws IOException {
252            File targetConfigDirectory = serverInfo.resolveServer(serverInstance.getConfigFile()).getParentFile();
253            FileUtils.forceMkdir(targetConfigDirectory);
254            org.apache.geronimo.system.plugin.ServerInstance instance = serverInstance.getServerInstance(artifactManager, targetRepo, serverInfo, servers, live);
255            servers.put(instance.getServerName(), instance);
256        }
257    
258        private static ConfigurationManager buildConfigurationManager(ArtifactManager artifactManager,
259                                                                      WritableListableRepository targetRepo,
260                                                                      Kernel kernel,
261                                                                      ConfigurationStore targetStore,
262                                                                      ClassLoader classloader,
263                                                                      Map<String, org.apache.geronimo.system.plugin.ServerInstance> servers) throws IOException {
264            for (ServerInstance instance : servers.values()) {
265                if ("default".equals(instance.getServerName())) {
266                    KernelConfigurationManager configurationManager = new KernelConfigurationManager(kernel,
267                            Collections.singleton(targetStore),
268                            instance.getAttributeStore(),
269                            (PersistentConfigurationList) instance.getAttributeStore(),
270                            artifactManager,
271                            instance.getArtifactResolver(),
272                            Collections.singleton(targetRepo),
273                            null,
274                            classloader);
275                    configurationManager.setOnline(false);
276                    return configurationManager;
277                }
278            }
279            throw new IllegalStateException("No default server instance found: " + servers.keySet());
280        }
281    
282        /* now for tests only */
283        PluginInstallerGBean(ConfigurationManager configManager, WritableListableRepository repository, ConfigurationStore configStore, ServerInfo serverInfo, ThreadPool threadPool, Collection<ServerInstance> servers) {
284            this.configManager = configManager;
285            localSourceRepository = new GeronimoSourceRepository(configManager.getRepositories(), configManager.getArtifactResolver());
286            this.writeableRepo = repository;
287            this.configStore = configStore;
288            this.serverInfo = serverInfo;
289            this.threadPool = threadPool;
290            asyncKeys = Collections.synchronizedMap(new HashMap<Object, DownloadResults>());
291            serverInstanceDatas = null;
292            this.persistentConfigurationLists = Collections.emptyList();
293            classLoader = null;
294            for (ServerInstance instance : servers) {
295                this.servers.put(instance.getServerName(), instance);
296            }
297            if (servers instanceof ReferenceCollection) {
298                ((ReferenceCollection) servers).addReferenceCollectionListener(new ReferenceCollectionListener() {
299    
300                    public void memberAdded(ReferenceCollectionEvent event) {
301                        ServerInstance instance = (ServerInstance) event.getMember();
302                        PluginInstallerGBean.this.servers.put(instance.getServerName(), instance);
303                    }
304    
305                    public void memberRemoved(ReferenceCollectionEvent event) {
306                        ServerInstance instance = (ServerInstance) event.getMember();
307                        PluginInstallerGBean.this.servers.remove(instance.getServerName());
308                    }
309                });
310            }
311        }
312    
313        /**
314         * This more or less clones the current PluginInstallerGBean to create one with the same server instances (structure) but using
315         * the current server as config store and assembling a server in a provided location.
316         *
317         * @param targetRepositoryPath     location of repository in new server (normally "repository")
318         * @param relativeTargetServerPath Location of server to assemble relative to current server
319         * @param pluginList               list of plugins to install
320         * @throws Exception if something goes wrong
321         */
322        public DownloadResults installPluginList(String targetRepositoryPath, String relativeTargetServerPath, PluginListType pluginList) throws Exception {
323            DownloadResults downloadPoller = new DownloadResults();
324            File targetServerPath = serverInfo.resolveServer(relativeTargetServerPath);
325            if (targetServerPath.exists()) {
326                    FileUtils.forceDelete(targetServerPath);
327            }
328            String targetServerPathName = targetServerPath.getAbsolutePath();
329            Kernel kernel = new BasicKernel("assembly");
330    
331            try {
332    //            kernel.boot();
333                PluginInstallerGBean installer = new PluginInstallerGBean(
334                        targetRepositoryPath,
335                        targetServerPathName,
336                        serverInstanceDatas,
337                        kernel,
338                        classLoader);
339    
340                installer.install(pluginList, localSourceRepository, true, null, null, downloadPoller);
341            } finally {
342                kernel.shutdown();
343            }
344            return downloadPoller;
345        }
346    
347        public void mergeOverrides(String server, AttributesType overrides) throws InvalidGBeanException, IOException {
348            ServerInstance serverInstance = servers.get(server);
349            if (serverInstance == null) {
350                throw new NullPointerException("No such server: " + server + ", known servers: " + servers.keySet());
351            }
352            PluginAttributeStore attributeStore = serverInstance.getAttributeStore();
353            for (ModuleType module: overrides.getModule()) {
354                Artifact artifact = Artifact.create(module.getName());
355                attributeStore.setModuleGBeans(artifact, module.getGbean(), module.isLoad(), module.getCondition());
356                attributeStore.save();
357            }
358            if (overrides.getConfiguration().size() > 0) {
359                throw new UnsupportedOperationException("Use modules, not configurations");
360            }
361        }
362    
363    
364        /**
365         * Lists the plugins installed in the local Geronimo server, by name and
366         * ID.
367         *
368         * @return A Map with key type String (plugin name) and value type Artifact
369         *         (config ID of the plugin).
370         */
371        public Map getInstalledPlugins() {
372            SortedSet<Artifact> artifacts = writeableRepo.list();
373    
374            Map<String, Artifact> plugins = new HashMap<String, Artifact>();
375            for (Artifact configId : artifacts) {
376                File dir = writeableRepo.getLocation(configId);
377                if (dir.isDirectory()) {
378                    File meta = new File(dir, "META-INF");
379                    if (!meta.isDirectory() || !meta.canRead()) {
380                        continue;
381                    }
382                    File xml = new File(meta, "geronimo-plugin.xml");
383                    if (!xml.isFile() || !xml.canRead() || xml.length() == 0) {
384                        continue;
385                    }
386                    readNameAndID(xml, plugins);
387                } else {
388                    if (!dir.isFile() || !dir.canRead()) {
389                        log.error("Cannot read artifact dir " + dir.getAbsolutePath());
390                        throw new IllegalStateException("Cannot read artifact dir " + dir.getAbsolutePath());
391                    }
392                    try {
393                        JarFile jar = new JarFile(dir);
394                        try {
395                            ZipEntry entry = jar.getEntry("META-INF/geronimo-plugin.xml");
396                            if (entry == null) {
397                                continue;
398                            }
399                            InputStream in = jar.getInputStream(entry);
400                            readNameAndID(in, plugins);
401                            in.close();
402                        } finally {
403                            jar.close();
404                        }
405                    } catch (IOException e) {
406                        log.error("Unable to read JAR file " + dir.getAbsolutePath(), e);
407                    }
408                }
409            }
410            return plugins;
411        }
412    
413        /**
414         * Gets a ConfigurationMetadata for a configuration installed in the local
415         * server.  Should load a saved one if available, or else create a new
416         * default one to the best of its abilities.
417         *
418         * @param moduleId Identifies the configuration.  This must match a
419         *                 configuration currently installed in the local server.
420         *                 The configId must be fully resolved (isResolved() == true)
421         */
422        public PluginType getPluginMetadata(Artifact moduleId) {
423            PluginType type = localSourceRepository.extractPluginMetadata(moduleId);
424            if (null == type) {
425                try {
426                    type = createDefaultMetadata(moduleId);
427                } catch (InvalidConfigException e) {
428                    log.warn("Unable to generate metadata for " + moduleId, e);
429                } catch (Exception e) {
430                    log.warn("Error generating metadata for " + moduleId, e);
431                }
432            }
433            return type;
434        }
435    
436        /**
437         * Saves a ConfigurationMetadata for a particular plugin, if the server is
438         * able to record it.  This can be used if you later re-export the plugin,
439         * or just want to review the information for a particular installed
440         * plugin.
441         *
442         * @param metadata The data to save.  The contained configId (which must
443         *                 be fully resolved) identifies the configuration to save
444         *                 this for.
445         */
446        public void updatePluginMetadata(PluginType metadata) {
447            PluginArtifactType instance = metadata.getPluginArtifact().get(0);
448            Artifact artifact = toArtifact(instance.getModuleId());
449            File dir = writeableRepo.getLocation(artifact);
450            if (dir == null) {
451                log.error(artifact + " is not installed.");
452                throw new IllegalArgumentException(artifact + " is not installed.");
453            }
454            if (!dir.isDirectory()) { // must be a packed (JAR-formatted) plugin
455                try {
456                    File temp = new File(dir.getParentFile(), dir.getName() + ".temp");
457                    JarFile input = new JarFile(dir);
458                    Manifest manifest = input.getManifest();
459                    JarOutputStream out = manifest == null ? new JarOutputStream(
460                            new BufferedOutputStream(new FileOutputStream(temp)))
461                            : new JarOutputStream(new BufferedOutputStream(new FileOutputStream(temp)), manifest);
462                    Enumeration en = input.entries();
463                    byte[] buf = new byte[4096];
464                    int count;
465                    while (en.hasMoreElements()) {
466                        JarEntry entry = (JarEntry) en.nextElement();
467                        if (entry.getName().equals("META-INF/geronimo-plugin.xml")) {
468                            entry = new JarEntry(entry.getName());
469                            out.putNextEntry(entry);
470                            PluginXmlUtil.writePluginMetadata(metadata, out);
471                        } else if (entry.getName().equals("META-INF/MANIFEST.MF")) {
472                            // do nothing, already passed in a manifest
473                        } else {
474                            out.putNextEntry(entry);
475                            InputStream in = input.getInputStream(entry);
476                            while ((count = in.read(buf)) > -1) {
477                                out.write(buf, 0, count);
478                            }
479                            in.close();
480                            out.closeEntry();
481                        }
482                    }
483                    out.flush();
484                    out.close();
485                    input.close();
486                    if (!dir.delete()) {
487                        log.error("Unable to delete old plugin at " + dir.getAbsolutePath());
488                        throw new IOException("Unable to delete old plugin at " + dir.getAbsolutePath());
489                    }
490                    if (!temp.renameTo(dir)) {
491                        log.error("Unable to move new plugin " + temp.getAbsolutePath() + " to " + dir.getAbsolutePath());
492                        throw new IOException(
493                                "Unable to move new plugin " + temp.getAbsolutePath() + " to " + dir.getAbsolutePath());
494                    }
495                } catch (Exception e) {
496                    log.error("Unable to update plugin metadata", e);
497                    throw new RuntimeException("Unable to update plugin metadata", e);
498                } // TODO this really should have a finally block to ensure streams are closed
499            } else {
500                File meta = new File(dir, "META-INF");
501                if (!meta.isDirectory() || !meta.canRead()) {
502                    log.error(artifact + " is not a plugin.");
503                    throw new IllegalArgumentException(artifact + " is not a plugin.");
504                }
505                File xml = new File(meta, "geronimo-plugin.xml");
506                FileOutputStream fos = null;
507                try {
508                    if (!xml.isFile()) {
509                        if (!xml.createNewFile()) {
510                            log.error("Cannot create plugin metadata file for " + artifact);
511                            throw new RuntimeException("Cannot create plugin metadata file for " + artifact);
512                        }
513                    }
514                    fos = new FileOutputStream(xml);
515                    PluginXmlUtil.writePluginMetadata(metadata, fos);
516                } catch (Exception e) {
517                    log.error("Unable to save plugin metadata for " + artifact, e);
518                } finally {
519                    if (fos != null) {
520                        try {
521                            fos.close();
522                        } catch (IOException ignored) {
523                            // ignored
524                        }
525                    }
526                }
527            }
528        }
529    
530        /**
531         * Lists the plugins available for download in a particular Geronimo repository.
532         *
533         * @param mavenRepository The base URL to the maven repository.  This must
534         *                        contain the file geronimo-plugins.xml
535         * @param username        Optional username, if the maven repo uses HTTP Basic authentication.
536         *                        Set this to null if no authentication is required.
537         * @param password        Optional password, if the maven repo uses HTTP Basic authentication.
538         *                        Set this to null if no authentication is required.
539         */
540        public PluginListType listPlugins(URL mavenRepository, String username, String password) throws IOException, FailedLoginException {
541            try {
542                SourceRepository repo = SourceRepositoryFactory.getSourceRepository(mavenRepository.toString(), username, password);
543                return repo.getPluginList();
544            } catch (IllegalStateException e) {
545                return null;
546            }
547        }
548    
549        private SourceRepository getDefaultSourceRepository(String defaultRepository,
550                                                            boolean restrictToDefaultRepository) {
551            if (restrictToDefaultRepository && defaultRepository == null) {
552                throw new IllegalArgumentException("You must supply a default repository if you want to restrict to it");
553            }
554            SourceRepository defaultSourceRepository = defaultRepository == null ? null : SourceRepositoryFactory.getSourceRepository(defaultRepository);
555            return defaultSourceRepository;
556        }
557        
558        /**
559         * Installs a configuration from a remote repository into the local Geronimo server,
560         * including all its dependencies.  The caller will get the results when the
561         * operation completes.  Note that this method does not throw exceptions on failure,
562         * but instead sets the failure property of the DownloadResults.
563         *
564         * @param pluginsToInstall            The list of configurations to install
565         * @param defaultRepository           Default repo to look for plugins in
566         * @param restrictToDefaultRepository Whether to follow hints to other plugin repos.
567         * @param username                    Optional username, if the maven repo uses HTTP Basic authentication.
568         *                                    Set this to null if no authentication is required.
569         * @param password                    Optional password, if the maven repo uses HTTP Basic authentication.
570         *                                    Set this to null if no authentication is required.
571         */
572        public DownloadResults install(PluginListType pluginsToInstall, String defaultRepository, boolean restrictToDefaultRepository, String username, String password) {
573            DownloadResults results = new DownloadResults();
574            install(pluginsToInstall, defaultRepository, restrictToDefaultRepository, username, password, results);
575            return results;
576        }
577    
578        /**
579         * Installs a configuration from a remote repository into the local Geronimo server,
580         * including all its dependencies.  The method blocks until the operation completes,
581         * but the caller will be notified of progress frequently along the way (using the
582         * supplied DownloadPoller).  Therefore the caller is meant to create the poller and
583         * then call this method in a background thread.  Note that this method does not
584         * throw exceptions on failure, but instead sets the failure property of the
585         * DownloadPoller.
586         *
587         * @param pluginsToInstall            The list of configurations to install
588         * @param defaultRepository           Default repo to look for plugins in (not required)
589         * @param restrictToDefaultRepository Whether to follow hints to other plugin repos.
590         * @param username                    Optional username, if the maven repo uses HTTP Basic authentication.
591         *                                    Set this to null if no authentication is required.
592         * @param password                    Optional password, if the maven repo uses HTTP Basic authentication.
593         *                                    Set this to null if no authentication is required.
594         * @param poller                      Will be notified with status updates as the download proceeds
595         */
596        public void install(PluginListType pluginsToInstall, String defaultRepository, boolean restrictToDefaultRepository, String username, String password, DownloadPoller poller) {
597            SourceRepository defaultSourceRepository = getDefaultSourceRepository(defaultRepository, restrictToDefaultRepository);
598            install(pluginsToInstall, defaultSourceRepository, restrictToDefaultRepository, username, password, poller);
599        }
600    
601        public void install(PluginListType pluginsToInstall, SourceRepository defaultRepository, boolean restrictToDefaultRepository, String username, String password, DownloadPoller poller) {
602            install(pluginsToInstall, defaultRepository, restrictToDefaultRepository, username, password, poller, true);
603        }
604        
605        public void install(PluginListType pluginsToInstall, SourceRepository defaultRepository, boolean restrictToDefaultRepository, String username, String password, DownloadPoller poller, boolean validatePlugins) {
606            List<Artifact> downloadedArtifacts = new ArrayList<Artifact>();
607            try {
608                Map<Artifact, PluginType> metaMap = new HashMap<Artifact, PluginType>();
609                // Step 1: validate everything
610                List<PluginType> toInstall = new ArrayList<PluginType>();
611                for (PluginType metadata : pluginsToInstall.getPlugin()) {
612                    try {
613                        if (validatePlugins) {
614                            validatePlugin(metadata);
615                            verifyPrerequisites(metadata);
616                        }
617    
618                        PluginArtifactType instance = metadata.getPluginArtifact().get(0);
619    
620                        if (instance.getModuleId() != null) {
621                            metaMap.put(toArtifact(instance.getModuleId()), metadata);
622                        }
623                        toInstall.add(metadata);
624                    } catch (MissingDependencyException e) {
625                        poller.addSkippedConfigID(e);
626                    }
627                }
628    
629                // Step 2: everything is valid, do the installation
630                for (PluginType metadata : toInstall) {
631                    // 2. Unload obsoleted configurations
632                    PluginArtifactType instance = metadata.getPluginArtifact().get(0);
633                    List<Artifact> obsoletes = new ArrayList<Artifact>();
634                    for (ArtifactType obs : instance.getObsoletes()) {
635                        Artifact obsolete = toArtifact(obs);
636                        Artifact[] list = configManager.getArtifactResolver().queryArtifacts(obsolete);
637                        for (Artifact artifact : list) {
638                            if (configManager.isLoaded(artifact)) {
639                                if (configManager.isRunning(artifact)) {
640                                    configManager.stopConfiguration(artifact);
641                                }
642                                configManager.unloadConfiguration(artifact);
643                                obsoletes.add(artifact);
644                            }
645                        }
646                    }
647                    // 3. Download the artifact if necessary, and its dependencies
648                    Set<Artifact> working = new HashSet<Artifact>();
649                    Stack<Artifact> parentStack = new Stack<Artifact>();
650                    if (instance.getModuleId() != null) {
651                        Artifact entry = toArtifact(instance.getModuleId());
652                        List<SourceRepository> repos = getRepos(pluginsToInstall, defaultRepository, restrictToDefaultRepository, instance);
653                        downloadArtifact(entry, metaMap, repos,
654                                username, password, new ResultsFileWriteMonitor(poller), working, parentStack, false, servers, true);
655                        downloadedArtifacts.add(entry);
656                    } else {
657                        List<DependencyType> deps = instance.getDependency();
658                        for (DependencyType dep : deps) {
659                            Artifact entry = toArtifact(dep);
660                            List<SourceRepository> repos = getRepos(pluginsToInstall, defaultRepository, restrictToDefaultRepository, instance);
661                            downloadArtifact(entry, metaMap, repos,
662                                    username, password, new ResultsFileWriteMonitor(poller), working, parentStack, false, servers, dep.isStart());
663                            downloadedArtifacts.add(entry);
664                        }
665                    }
666                    // 4. Uninstall obsolete configurations
667                    for (Artifact artifact : obsoletes) {
668                        configManager.uninstallConfiguration(artifact);
669                    }
670                    // 5. Installation of this configuration finished successfully
671                }
672    
673                // Step 3: Start anything that's marked accordingly
674                if (configManager.isOnline()) {
675                    poller.setCurrentFilePercent(-1);
676                    for (PersistentConfigurationList persistentConfigurationList : persistentConfigurationLists) {
677                        List<Artifact> artifacts = persistentConfigurationList.restore();
678                        for (Artifact artifact : artifacts) {
679                            if (!configManager.isRunning(artifact)) {
680                                poller.setCurrentMessage("Starting " + artifact);
681                                if (!configManager.isLoaded(artifact)) {
682                                    configManager.loadConfiguration(artifact);
683                                }
684                                configManager.startConfiguration(artifact);
685                            }
686                        }
687                    }
688                }
689                //ensure config.xml is saved.
690                for (org.apache.geronimo.system.plugin.ServerInstance serverInstance : servers.values()) {
691                    serverInstance.getAttributeStore().save();
692                }
693            } catch (Exception e) {
694                log.error("Unable to install plugin. ", e);
695                poller.setFailure(e);
696                //Attempt to cleanup a failed plugin installation
697                for (Artifact artifact : downloadedArtifacts) {
698                    try {
699                        configManager.uninstallConfiguration(artifact);
700                    } catch (Exception e2) {
701                        log.warn("Warning: ",e2);
702                    }
703                }
704            } finally {
705                poller.setFinished();
706            }
707        }
708    
709        private List<SourceRepository> getRepos(PluginListType pluginsToInstall, SourceRepository defaultRepository, boolean restrictToDefaultRepository, PluginArtifactType instance) {
710            List<SourceRepository> repos = new ArrayList<SourceRepository>();
711            if (defaultRepository != null) {
712                repos.add(defaultRepository);
713            }
714            if (!restrictToDefaultRepository) {
715                List<String> repoLocations;
716                if (!instance.getSourceRepository().isEmpty()) {
717                    repoLocations = instance.getSourceRepository();
718                } else {
719                    repoLocations = pluginsToInstall.getDefaultRepository();
720                }
721                for (String repoLocation : repoLocations) {
722                    SourceRepository repo = SourceRepositoryFactory.getSourceRepository(repoLocation);
723                    repos.add(repo);
724                }
725            }
726            return repos;
727        }
728    
729        /**
730         * Installs a configuration from a remote repository into the local Geronimo server,
731         * including all its dependencies.  The method returns immediately, providing a key
732         * that can be used to poll the status of the download operation.  Note that the
733         * installation does not throw exceptions on failure, but instead sets the failure
734         * property of the DownloadResults that the caller can poll for.
735         *
736         * @param pluginsToInstall            The list of configurations to install
737         * @param defaultRepository           Default repo to look for plugins in
738         * @param restrictToDefaultRepository Whether to follow hints to other plugin repos.
739         * @param username                    Optional username, if the maven repo uses HTTP Basic authentication.
740         *                                    Set this to null if no authentication is required.
741         * @param password                    Optional password, if the maven repo uses HTTP Basic authentication.
742         *                                    Set this to null if no authentication is required. @return A key that can be passed to checkOnInstall
743         */
744        public Object startInstall(final PluginListType pluginsToInstall, final String defaultRepository, final boolean restrictToDefaultRepository, final String username, final String password) {
745            Object key = getNextKey();
746            final DownloadResults results = new DownloadResults();
747            Runnable work = new Runnable() {
748                public void run() {
749                    install(pluginsToInstall, defaultRepository, restrictToDefaultRepository, username, password, results);
750                }
751            };
752            asyncKeys.put(key, results);
753            try {
754                threadPool.execute("Configuration Installer", work);
755            } catch (InterruptedException e) {
756                log.error("Unable to start work", e);
757                throw new RuntimeException("Unable to start work", e);
758            }
759            return key;
760        }
761    
762        /**
763         * Installs a configuration downloaded from a remote repository into the local Geronimo
764         * server, including all its dependencies.  The method returns immediately, providing a
765         * key that can be used to poll the status of the download operation.  Note that the
766         * installation does not throw exceptions on failure, but instead sets the failure
767         * property of the DownloadResults that the caller can poll for.
768         *
769         * @param carFile                     A CAR file downloaded from a remote repository.  This is a packaged
770         *                                    configuration with included configuration information, but it may
771         *                                    still have external dependencies that need to be downloaded
772         *                                    separately.  The metadata in the CAR file includes a repository URL
773         *                                    for these downloads, and the username and password arguments are
774         *                                    used in conjunction with that.
775         * @param defaultRepository           Default repo to look for plugins in
776         * @param restrictToDefaultRepository Whether to follow hints to other plugin repos.
777         * @param username                    Optional username, if the maven repo uses HTTP Basic authentication.
778         *                                    Set this to null if no authentication is required.
779         * @param password                    Optional password, if the maven repo uses HTTP Basic authentication.
780         *                                    Set this to null if no authentication is required. @return A key that can be passed to checkOnInstall
781         */
782        public Object startInstall(final File carFile, final String defaultRepository, final boolean restrictToDefaultRepository, final String username, final String password) {
783            Object key = getNextKey();
784            final DownloadResults results = new DownloadResults();
785            Runnable work = new Runnable() {
786                public void run() {
787                    install(carFile, defaultRepository, restrictToDefaultRepository, username, password, results);
788                }
789            };
790            asyncKeys.put(key, results);
791            try {
792                threadPool.execute("Configuration Installer", work);
793            } catch (InterruptedException e) {
794                log.error("Unable to start work", e);
795                throw new RuntimeException("Unable to start work", e);
796            }
797            return key;
798        }
799    
800        /**
801         * Gets the current progress of a download operation.  Note that once the
802         * DownloadResults is returned for this operation shows isFinished = true,
803         * the operation will be forgotten, so the caller should be careful not to
804         * call this again after the download has finished.
805         *
806         * @param key Identifies the operation to check on
807         */
808        public DownloadResults checkOnInstall(Object key) {
809            return checkOnInstall(key, true);
810        }
811        
812        /**
813         * Gets the current progress of a download operation.  
814         *
815         * @param key Identifies the operation to check on
816         * @param remove If true and the download operation has finished, the DownloadResults
817         *        will be forgotten and the next call to this function will return null. 
818         *        Otherwise, the DownloadResults will be retained until this function is 
819         *        called with the <tt>remove</tt> parameter set to true. This parameter is
820         *        only used when the download operation has finished 
821         *        (DownloadResults.isFinished() returns true).
822         */
823        public DownloadResults checkOnInstall(Object key, boolean remove) {
824            DownloadResults results = asyncKeys.get(key);
825            if (results != null) {               
826                results = results.duplicate();
827                if (results.isFinished() && remove) {
828                    asyncKeys.remove(key);
829                }
830            }
831            return results;
832        }
833    
834        /**
835         * Installs from a pre-downloaded CAR file
836         *
837         * @param carFile                     care file to install
838         * @param defaultRepository           Default repo to look for plugins in
839         * @param restrictToDefaultRepository Whether to follow hints to other plugin repos.
840         * @param username                    repo username
841         * @param password                    repo password
842         * @param poller                      monitor for reporting progress
843         */
844        public void install(File carFile, String defaultRepository, boolean restrictToDefaultRepository, String username, String password, DownloadPoller poller) {
845            try {
846                // 1. Extract the configuration metadata
847                PluginType data = GeronimoSourceRepository.extractPluginMetadata(carFile);
848                if (data == null) {
849                    log.error("Invalid Configuration Archive " + carFile.getAbsolutePath() + " no plugin metadata found");
850                    throw new IllegalArgumentException(
851                            "Invalid Configuration Archive " + carFile.getAbsolutePath() + " no plugin metadata found");
852                }
853    
854                // 2. Validate that we can install this
855                validatePlugin(data);
856                verifyPrerequisites(data);
857                
858                PluginArtifactType instance = data.getPluginArtifact().get(0);
859                // 3. Install the CAR into the repository (it shouldn't be re-downloaded)
860                if (instance.getModuleId() != null) {
861                    Artifact pluginArtifact = toArtifact(instance.getModuleId());
862                    ResultsFileWriteMonitor monitor = new ResultsFileWriteMonitor(poller);
863                    writeableRepo.copyToRepository(carFile, pluginArtifact, monitor);
864                    installConfigXMLData(pluginArtifact, instance, servers, true);
865                    if (instance.getCopyFile() != null) {
866                        extractPluginFiles(pluginArtifact, data, monitor);
867                    }
868                }
869    
870                // 4. Use the standard logic to remove obsoletes, install dependencies, etc.
871                PluginListType pluginList = new PluginListType();
872                pluginList.getPlugin().add(data);
873                pluginList.getDefaultRepository().addAll(instance.getSourceRepository());
874                
875                SourceRepository defaultSourceRepository = getDefaultSourceRepository(defaultRepository, restrictToDefaultRepository);
876                
877                install(pluginList, defaultSourceRepository, restrictToDefaultRepository, username, password, poller, false);
878            } catch (Exception e) {
879                poller.setFailure(e);
880            } finally {
881                poller.setFinished();
882            }
883        }
884    
885        /**
886         * Ensures that a plugin is installable.
887         *
888         * @param plugin plugin to check
889         * @throws org.apache.geronimo.kernel.repository.MissingDependencyException
890         *          if plugin requires a dependency that is not present
891         */
892        public void validatePlugin(PluginType plugin) throws MissingDependencyException {
893            if (plugin.getPluginArtifact().size() != 1) {
894                throw new MissingDependencyException("A plugin configuration must include one plugin artifact, not " + plugin.getPluginArtifact().size(), null, (Stack<Artifact>) null);
895            }
896            PluginArtifactType metadata = plugin.getPluginArtifact().get(0);
897            // 1. Check that it's not already installed
898            if (metadata.getModuleId() != null) { // that is, it's a real configuration not a plugin list
899                Artifact artifact = toArtifact(metadata.getModuleId());
900                if (configManager.isInstalled(artifact)) {
901                    boolean upgrade = false;
902                    for (ArtifactType obsolete : metadata.getObsoletes()) {
903                        Artifact test = toArtifact(obsolete);
904                        if (test.matches(artifact)) {
905                            upgrade = true;
906                            break;
907                        }
908                    }
909                    if (!upgrade) {
910                        log.debug("Configuration " + artifact + " is already installed.");
911                        throw new MissingDependencyException(
912                                "Configuration " + artifact + " is already installed.", toArtifact(metadata.getModuleId()), (Stack<Artifact>) null);
913                    }
914                }
915            }
916    
917            // 2. Check that we meet the Geronimo, JVM versions
918            if (metadata.getGeronimoVersion().size() > 0 && !checkGeronimoVersions(metadata.getGeronimoVersion())) {
919                log.debug("Plugin " + toArtifact(metadata.getModuleId()) + " is not installable on Geronimo " + serverInfo.getVersion());
920                throw new MissingDependencyException(
921                        "Plugin is not installable on Geronimo " + serverInfo.getVersion(), toArtifact(metadata.getModuleId()), (Stack<Artifact>) null);
922            }
923            if (metadata.getJvmVersion().size() > 0 && !checkJVMVersions(metadata.getJvmVersion())) {
924                log.debug("Plugin " + toArtifact(metadata.getModuleId()) + " is not installable on JVM " + System.getProperty("java.version"));
925                throw new MissingDependencyException(
926                        "Plugin is not installable on JVM " + System.getProperty("java.version"), toArtifact(metadata.getModuleId()), (Stack<Artifact>) null);
927            }
928        }
929    
930    
931        /**
932         * Ensures that a plugin's prerequisites are installed
933         *
934         * @param plugin plugin artifact to check
935         * @return array of missing depedencies
936         */
937        public Dependency[] checkPrerequisites(PluginType plugin) {
938            List<Dependency> missingPrereqs = getMissingPrerequisites(plugin);
939            return missingPrereqs.toArray(new Dependency[missingPrereqs.size()]);
940        }
941    
942        private List<Dependency> getMissingPrerequisites(PluginType plugin) {
943            if (plugin.getPluginArtifact().size() != 1) {
944                throw new IllegalArgumentException("A plugin configuration must include one plugin artifact, not " + plugin.getPluginArtifact().size());
945            }
946    
947            PluginArtifactType metadata = plugin.getPluginArtifact().get(0);
948            List<PrerequisiteType> prereqs = metadata.getPrerequisite();
949    
950            ArrayList<Dependency> missingPrereqs = new ArrayList<Dependency>();
951            for (PrerequisiteType prereq : prereqs) {
952                Artifact artifact = toArtifact(prereq.getId());
953                try {
954                    if (getServerInstance("default", servers).getArtifactResolver().queryArtifacts(artifact).length == 0) {
955                        missingPrereqs.add(new Dependency(artifact, ImportType.ALL));
956                    }
957                } catch (NoServerInstanceException e) {
958                    throw new RuntimeException("Invalid setup, no default server instance registered");
959                }
960            }
961            return missingPrereqs;
962        }
963    
964        private void verifyPrerequisites(PluginType plugin) throws MissingDependencyException {
965            List<Dependency> missingPrereqs = getMissingPrerequisites(plugin);
966            if (!missingPrereqs.isEmpty()) {
967                PluginArtifactType metadata = plugin.getPluginArtifact().get(0);
968                Artifact moduleId = toArtifact(metadata.getModuleId());
969                StringBuffer buf = new StringBuffer();
970                buf.append(moduleId.toString()).append(" requires ");
971                Iterator<Dependency> iter = missingPrereqs.iterator();
972                while (iter.hasNext()) {
973                    buf.append(iter.next().getArtifact().toString());
974                    if (iter.hasNext()) {
975                        buf.append(", ");
976                    }
977                }
978                buf.append(" to be installed");
979                throw new MissingDependencyException(buf.toString(), null, (Artifact) null);
980            }
981        }
982    
983        public Artifact installLibrary(File libFile, String groupId) throws IOException {
984            Matcher matcher = MAVEN_1_PATTERN_PART.matcher("");
985            matcher.reset(libFile.getName());
986            if (matcher.matches()) {
987                String artifactId = matcher.group(1);
988                String version = matcher.group(2);
989                String type = matcher.group(3);
990                Artifact artifact = new Artifact(groupId != null ? groupId : Artifact.DEFAULT_GROUP_ID, artifactId, version, type);
991                writeableRepo.copyToRepository(libFile, artifact, null);
992                return artifact;
993            } else {
994                throw new IllegalArgumentException("Filename " + libFile.getName() + " is not in the form <artifact>-<version>.<type>, for e.g. mylib-1.0.jar.");
995            }
996        }
997    
998        /**
999         * Download (if necessary) and install something, which may be a Configuration or may
1000         * be just a JAR.  For each artifact processed, all its dependencies will be
1001         * processed as well.
1002         *
1003         * @param configID     Identifies the artifact to install
1004         * @param metadata     name to plugin map
1005         * @param repos        The URLs to contact the repositories (in order of preference)
1006         * @param username     The username used for repositories secured with HTTP Basic authentication
1007         * @param password     The password used for repositories secured with HTTP Basic authentication
1008         * @param monitor      The ongoing results of the download operations, with some monitoring logic
1009         * @param soFar        The set of dependencies already downloaded.
1010         * @param parentStack  chain of modules that led to this dependency
1011         * @param dependency   Is this a dependency or the original artifact?
1012         * @param servers      server layouts to install config info into
1013         * @param loadOverride If false prevents setting load="true" in server instances (recursively through dependencies)
1014         * @throws FailedLoginException       When a repository requires authentication and either no username
1015         *                                    and password are supplied or the username and password supplied
1016         *                                    are not accepted
1017         * @throws MissingDependencyException When a dependency cannot be located in any of the listed repositories
1018         * @throws NoServerInstanceException  when no server descriptor is found for a specified configuration bit
1019         * @throws java.io.IOException        when a IO problem occurs
1020         */
1021        private void downloadArtifact(Artifact configID, Map<Artifact, PluginType> metadata, List<SourceRepository> repos, String username, String password, ResultsFileWriteMonitor monitor, Set<Artifact> soFar, Stack<Artifact> parentStack, boolean dependency, Map<String, ServerInstance> servers, boolean loadOverride) throws IOException, FailedLoginException, MissingDependencyException, NoServerInstanceException {
1022            if (soFar.contains(configID)) {
1023                return; // Avoid endless work due to circular dependencies
1024            } else {
1025                soFar.add(configID);
1026            }
1027            // Download and install the main artifact
1028            boolean pluginWasInstalled = false;
1029            Artifact[] matches = configManager.getArtifactResolver().queryArtifacts(configID);
1030            PluginArtifactType instance = null;
1031            if (matches.length == 0) {
1032                // not present, needs to be downloaded
1033                monitor.getResults().setCurrentMessage("Downloading " + configID);
1034                monitor.getResults().setCurrentFilePercent(-1);
1035                OpenResult result = null;
1036                for (SourceRepository repository : repos) {
1037                    result = repository.open(configID, monitor);
1038                    if (result != null) {
1039                        break;
1040                    }
1041                }
1042                if (result == null) {
1043                    throw new IllegalArgumentException("Could not find " + configID + " in any repo");
1044                }
1045                // Check if the result is already in server's repository
1046                if (configManager.getArtifactResolver().queryArtifacts(result.getArtifact()).length > 0) {
1047                    String msg = "Not downloading " + configID + ". Query for " + configID + " resulted in " + result.getArtifact()
1048                            + " which is already available in server's repository.";
1049                    monitor.getResults().setCurrentMessage(msg);
1050                    log.info(msg);
1051                    result.close();
1052                    return;
1053                }
1054                try {
1055                    File tempFile = result.getFile();
1056                    if (tempFile == null) {
1057                        log.error("Null filehandle was returned for " + configID);
1058                        throw new IllegalArgumentException("Null filehandle was returned for " + configID);
1059                    }
1060                    PluginType pluginData = metadata.get(configID);
1061                    // Only bother with the hash if we got it from a source other than the download file itself
1062                    HashType hash = pluginData == null ? null : pluginData.getPluginArtifact().get(0).getHash();
1063                    if (hash != null) {
1064                        String actual = ConfigurationStoreUtil.getActualChecksum(tempFile, hash.getType());
1065                        if (!actual.equals(hash.getValue())) {
1066                            log.error(
1067                                    "File download incorrect (expected " + hash.getType() + " hash " + hash.getValue() + " but got " + actual + ")");
1068                            throw new IOException(
1069                                    "File download incorrect (expected " + hash.getType() + " hash " + hash.getValue() + " but got " + actual + ")");
1070                        }
1071                    }
1072                    // See if the download file has plugin metadata and use it in preference to what is in the catalog.
1073                    try {
1074                        PluginType realPluginData = GeronimoSourceRepository.extractPluginMetadata(tempFile);
1075                        if (realPluginData != null) {
1076                            pluginData = realPluginData;
1077                        }
1078                    } catch (Exception e) {
1079                        log.error("Unable to read plugin metadata: " + e.getMessage());
1080                        throw (IOException) new IOException(
1081                                "Unable to read plugin metadata: " + e.getMessage()).initCause(e);
1082                    }
1083                    if (pluginData != null) { // it's a plugin, not a plain JAR
1084                        validatePlugin(pluginData);
1085                        instance = pluginData.getPluginArtifact().get(0);
1086                    }
1087                    monitor.getResults().setCurrentMessage("Copying " + result.getArtifact() + " to the repository");
1088                    result.install(writeableRepo, monitor);
1089                    if (pluginData != null) {
1090                        installConfigXMLData(result.getArtifact(), instance, servers, loadOverride);
1091                    } else {
1092                        log.debug("No config XML data to install.");
1093                    }
1094                    if (dependency) {
1095                        monitor.getResults().addDependencyInstalled(configID);
1096                        configID = result.getArtifact();
1097                    } else {
1098                        configID = result.getArtifact();
1099                        monitor.getResults().addInstalledConfigID(configID);
1100                    }
1101                    pluginWasInstalled = true;
1102                    if (pluginData != null)
1103                        log.info("Installed plugin with moduleId=" + pluginData.getPluginArtifact().get(0).getModuleId() + " and name=" + pluginData.getName());
1104                    else
1105                        log.info("Installed artifact=" + configID);
1106                } catch (InvalidGBeanException e) {
1107                    log.error("Invalid gbean configuration ", e);
1108                    throw new IllegalStateException(
1109                            "Invalid GBean configuration: " + e.getMessage(), e);
1110    
1111                } finally {
1112                    //todo probably not needede
1113                    result.close();
1114                }
1115            } else {
1116                if (dependency) {
1117                    monitor.getResults().addDependencyPresent(configID);
1118                } else {
1119                    monitor.getResults().addInstalledConfigID(configID);
1120                }
1121            }
1122            // Download and install the dependencies
1123            try {
1124                if (!configID.isResolved()) {
1125                    // See if something's running
1126                    for (int i = matches.length - 1; i >= 0; i--) {
1127                        Artifact match = matches[i];
1128                        if (configStore.containsConfiguration(match) && configManager.isRunning(match)) {
1129                            log.debug("Found required configuration=" + match + " and it is running.");
1130                            return; // its dependencies must be OK
1131                        } else {
1132                            log.debug("Either required configuration=" + match + " is not installed or it is not running.");
1133                        }
1134                    }
1135                    // Go with something that's installed
1136                    configID = matches[matches.length - 1];
1137                }
1138                ConfigurationData data = null;
1139                if (configStore.containsConfiguration(configID)) {
1140                    if (configManager.isRunning(configID)) {
1141                        return; // its dependencies must be OK
1142                    }
1143                    log.debug("Loading configuration=" + configID);
1144                    data = configStore.loadConfiguration(configID);
1145                }
1146                // Download the dependencies
1147                parentStack.push(configID);
1148                if (instance == null) {
1149                    //no plugin metadata, guess with something else
1150                    Dependency[] dependencies = data == null ? getDependencies(writeableRepo, configID) : getDependencies(data);
1151                    for (Dependency dep : dependencies) {
1152                        Artifact artifact = dep.getArtifact();
1153                        log.debug("Attempting to download dependency=" + artifact + " for configuration=" + configID);
1154                        downloadArtifact(artifact, metadata, repos, username, password, monitor, soFar, parentStack, true, servers, loadOverride);
1155                    }
1156                } else {
1157                    //rely on plugin metadata if present.
1158                    List<DependencyType> deps = instance.getDependency();
1159                    for (DependencyType dep : deps) {
1160                        Artifact artifact = toArtifact(dep);
1161                        log.debug("Attempting to download dependency=" + artifact + " for configuration=" + configID);
1162                        downloadArtifact(artifact, metadata, repos, username, password, monitor, soFar, parentStack, true, servers, loadOverride & dep.isStart());
1163                    }
1164                }
1165                parentStack.pop();
1166            } catch (NoSuchConfigException e) {
1167                log.error("Installed configuration into repository but ConfigStore does not see it: " + e.getMessage());
1168                throw new IllegalStateException(
1169                        "Installed configuration into repository but ConfigStore does not see it: " + e.getMessage(), e);
1170            } catch (InvalidConfigException e) {
1171                log.error("Installed configuration into repository but ConfigStore cannot load it: " + e.getMessage());
1172                throw new IllegalStateException(
1173                        "Installed configuration into repository but ConfigStore cannot load it: " + e.getMessage(), e);
1174            }
1175            // Copy any files out of the artifact
1176            PluginType currentPlugin = getPluginMetadata(configID);
1177            if (pluginWasInstalled && currentPlugin != null) {
1178                extractPluginFiles(configID, currentPlugin, monitor);
1179            }
1180        }
1181    
1182        private void extractPluginFiles(Artifact configID, PluginType currentPlugin, ResultsFileWriteMonitor monitor) throws IOException {
1183            PluginArtifactType instance = currentPlugin.getPluginArtifact().get(0);
1184            for (CopyFileType data : instance.getCopyFile()) {
1185                monitor.getResults().setCurrentFilePercent(-1);
1186                monitor.getResults().setCurrentFile(data.getValue());
1187                monitor.getResults().setCurrentMessage("Copying " + data.getValue() + " from plugin to Geronimo installation");
1188                copyFile(data, configID);
1189            }
1190        }
1191    
1192        void copyFile(CopyFileType data, Artifact configID) throws IOException {
1193            Set<URL> set;
1194            String sourceFile = data.getValue().trim();
1195            try {
1196                set = configStore.resolve(configID, null, sourceFile);
1197            } catch (NoSuchConfigException e) {
1198                log.error("Unable to identify module " + configID + " to copy files from");
1199                throw new IllegalStateException("Unable to identify module " + configID + " to copy files from", e);
1200            }
1201            if (set.size() == 0) {
1202                log.error("Installed configuration into repository but cannot locate file to copy " + sourceFile);
1203                return;
1204            }
1205            if (set.iterator().next().getPath().endsWith("/")) {
1206                //directory, get all contents
1207                String pattern = sourceFile;
1208                if (pattern.length() == 0 || pattern.endsWith("/")) {
1209                    pattern = pattern + "**";
1210                } else {
1211                    pattern = pattern + "/**";
1212                }
1213                try {
1214                    set = new TreeSet<URL>(new Comparator<URL>() {
1215    
1216                        public int compare(URL o1, URL o2) {
1217                            return o1.getPath().compareTo(o2.getPath());
1218                        }
1219                    });
1220                    set.addAll(configStore.resolve(configID, null, pattern));
1221                } catch (NoSuchConfigException e) {
1222                    log.error("Unable to list directory " + pattern + " to copy files from");
1223                    throw new IllegalStateException("Unable to list directory " + pattern + " to copy files from", e);
1224                }
1225            }
1226            boolean relativeToServer = "server".equals(data.getRelativeTo());
1227            String destDir = data.getDestDir();
1228            File targetDir = relativeToServer ? serverInfo.resolveServer(destDir) : serverInfo.resolve(destDir);
1229    
1230    
1231            createDirectory(targetDir);
1232            URI targetURI = targetDir.toURI();
1233            if (!targetDir.isDirectory()) {
1234                log.error(
1235                        "Plugin install cannot write file " + data.getValue() + " to " + destDir + " because " + targetDir.getAbsolutePath() + " is not a directory");
1236                return;
1237            }
1238            if (!targetDir.canWrite()) {
1239                log.error(
1240                        "Plugin install cannot write file " + data.getValue() + " to " + destDir + " because " + targetDir.getAbsolutePath() + " is not writable");
1241                return;
1242            }
1243            int start = -1;
1244            for (URL url : set) {
1245                String path = url.getPath();
1246                if (start == -1) {
1247                    if (sourceFile.length() == 0 || sourceFile.endsWith("/")) {
1248                        if ("jar".equals(url.getProtocol())) {
1249                            start = path.lastIndexOf("!/") + 2 + sourceFile.length();
1250                        } else {
1251                            start = path.length();
1252                            //this entry needs nothing done
1253                            continue;
1254                        }
1255                    } else {
1256                        String remove = sourceFile;
1257                        int pos = sourceFile.lastIndexOf('/');
1258                        if (pos > -1) {
1259                            remove = sourceFile.substring(pos + 1, sourceFile.length());
1260                        }
1261                        start = path.lastIndexOf(remove);
1262                    }
1263                }
1264                path = path.substring(start);
1265                File target = new File(targetURI.resolve(path));
1266                if (!target.exists()) {
1267                    if (path.endsWith("/")) {
1268                        if (!target.mkdirs()) {
1269                            log.error("Plugin install cannot create directory " + target.getAbsolutePath());
1270                        }
1271                        continue;
1272                    }
1273                    if (!target.createNewFile()) {
1274                        log.error("Plugin install cannot create new file " + target.getAbsolutePath());
1275                        continue;
1276                    }
1277                }
1278                if (target.isDirectory()) {
1279                    continue;
1280                }
1281                if (!target.canWrite()) {
1282                    log.error("Plugin install cannot write to file " + target.getAbsolutePath());
1283                    continue;
1284                }
1285                copyFile(url.openStream(), new FileOutputStream(target));
1286            }
1287        }
1288    
1289        private static void createDirectory(File dir) throws IOException {
1290            if (dir != null && !dir.exists()) {
1291                boolean success = dir.mkdirs();
1292                if (!success) {
1293                    throw new IOException("Cannot create directory " + dir.getAbsolutePath());
1294                }
1295            }
1296        }
1297    
1298        private void copyFile(InputStream in, FileOutputStream out) throws IOException {
1299            byte[] buf = new byte[4096];
1300            int count;
1301            while ((count = in.read(buf)) > -1) {
1302                out.write(buf, 0, count);
1303            }
1304            in.close();
1305            out.flush();
1306            out.close();
1307        }
1308    
1309        /**
1310         * Used to get dependencies for a JAR
1311         *
1312         * @param repo     repository containing jar
1313         * @param artifact artifact to find dependencies of
1314         * @return dependencies of artifact in repository
1315         */
1316        private static Dependency[] getDependencies(Repository repo, Artifact artifact) {
1317            Set<Artifact> set = repo.getDependencies(artifact);
1318            Dependency[] results = new Dependency[set.size()];
1319            int index = 0;
1320            for (Artifact dep : set) {
1321                results[index] = new Dependency(dep, ImportType.CLASSES);
1322                ++index;
1323            }
1324            return results;
1325        }
1326    
1327        /**
1328         * Used to get dependencies for a Configuration
1329         *
1330         * @param data configuration data
1331         * @return dependencies of configuration
1332         */
1333        private static Dependency[] getDependencies(ConfigurationData data) {
1334            List<Dependency> dependencies = new ArrayList<Dependency>(data.getEnvironment().getDependencies());
1335            Collection<ConfigurationData> children = data.getChildConfigurations().values();
1336            for (ConfigurationData child : children) {
1337                dependencies.addAll(child.getEnvironment().getDependencies());
1338            }
1339            return dependencies.toArray(new Dependency[dependencies.size()]);
1340        }
1341    
1342        /**
1343         * Searches for an artifact in the listed repositories, where the artifact
1344         * may have wildcards in the ID.
1345         */
1346    
1347        /**
1348         * Puts the name and ID of a plugin into the argument map of plugins,
1349         * by reading the values out of the provided plugin descriptor file.
1350         *
1351         * @param xml     The geronimo-plugin.xml for this plugin
1352         * @param plugins The result map to populate
1353         */
1354        private void readNameAndID(File xml, Map<String, Artifact> plugins) {
1355            try {
1356                SAXParserFactory factory = XmlUtil.newSAXParserFactory();
1357                SAXParser parser = factory.newSAXParser();
1358                PluginNameIDHandler handler = new PluginNameIDHandler();
1359                parser.parse(xml, handler);
1360                if (handler.isComplete()) {
1361                    plugins.put(handler.getName(), Artifact.create(handler.getID()));
1362                }
1363            } catch (Exception e) {
1364                log.warn("Invalid XML at " + xml.getAbsolutePath(), e);
1365            }
1366        }
1367    
1368        /**
1369         * Puts the name and ID of a plugin into the argument map of plugins,
1370         * by reading the values out of the provided plugin descriptor stream.
1371         *
1372         * @param xml     The geronimo-plugin.xml for this plugin
1373         * @param plugins The result map to populate
1374         */
1375        private void readNameAndID(InputStream xml, Map plugins) {
1376            try {
1377                SAXParserFactory factory = XmlUtil.newSAXParserFactory();
1378                SAXParser parser = factory.newSAXParser();
1379                PluginNameIDHandler handler = new PluginNameIDHandler();
1380                parser.parse(xml, handler);
1381                if (handler.isComplete()) {
1382                    plugins.put(handler.getName(), Artifact.create(handler.getID()));
1383                }
1384            } catch (Exception e) {
1385                log.warn("Invalid XML", e);
1386            }
1387        }
1388    
1389        /**
1390         * TODO figure out where this  can be called -- perhaps when installing a plugin?
1391         * Generates a default plugin metadata based on the data for this module
1392         * in the server.
1393         */
1394        private PluginType createDefaultMetadata(Artifact moduleId) throws InvalidConfigException, IOException, NoSuchConfigException {
1395            if (configManager != null) {
1396                if (!configManager.isConfiguration(moduleId)) {
1397                    return null;
1398                }
1399            } else {
1400                if (!configStore.containsConfiguration(moduleId)) {
1401                    return null;
1402                }
1403            }
1404            ConfigurationData data = configStore.loadConfiguration(moduleId);
1405    
1406            PluginType meta = new PluginType();
1407            PluginArtifactType instance = new PluginArtifactType();
1408            meta.getPluginArtifact().add(instance);
1409            meta.setName(toArtifactType(moduleId).getArtifactId());
1410            instance.setModuleId(toArtifactType(moduleId));
1411            meta.setCategory("Unknown");
1412            instance.getGeronimoVersion().add(serverInfo.getVersion());
1413            instance.getObsoletes().add(toArtifactType(new Artifact(moduleId.getGroupId(),
1414                    moduleId.getArtifactId(),
1415                    (Version) null,
1416                    moduleId.getType())));
1417            List<DependencyType> deps = instance.getDependency();
1418            addGeronimoDependencies(data, deps, true);
1419            return meta;
1420        }
1421    
1422        /**
1423         * Check whether the specified JVM versions match the current runtime
1424         * environment.
1425         *
1426         * @param jvmVersions allowed jvm versions
1427         * @return true if the specified versions match the current
1428         *         execution environment as defined by plugins-1.2.xsd
1429         */
1430        private boolean checkJVMVersions(List<String> jvmVersions) {
1431            if (jvmVersions.size() == 0) return true;
1432            String version = System.getProperty("java.version");
1433            boolean match = false;
1434            for (String jvmVersion : jvmVersions) {
1435                if (jvmVersion == null || jvmVersion.equals("")) {
1436                    log.error("jvm-version should not be empty.");
1437                    throw new IllegalStateException("jvm-version should not be empty.");
1438                }
1439                if (version.startsWith(jvmVersion)) {
1440                    match = true;
1441                    break;
1442                }
1443            }
1444            return match;
1445        }
1446    
1447        /**
1448         * Check whether the specified Geronimo versions match the current runtime
1449         * environment.
1450         *
1451         * @param gerVersions geronimo versions allowed by the plugin
1452         * @return true if the specified versions match the current
1453         *         execution environment as defined by plugins-1.2.xsd
1454         * @throws IllegalStateException if match does not work.
1455         */
1456        private boolean checkGeronimoVersions(List<String> gerVersions) throws IllegalStateException {
1457            if ((gerVersions == null) || (gerVersions.size() == 0)) {
1458                return true;
1459            }
1460    
1461            boolean match = false;
1462            for (String gerVersion : gerVersions) {
1463                match = checkGeronimoVersion(gerVersion);
1464                if (match) {
1465                    break;
1466                }
1467            }
1468            return match;
1469        }
1470    
1471        /**
1472         * Check whether the specified Geronimo version matches the current runtime
1473         * environment.
1474         *
1475         * @param gerVersion geronimo version to check against this server
1476         * @return true if the specified version matches the current
1477         *         execution environment as defined by plugins-1.2.xsd
1478         * @throws IllegalStateException if input is malformed
1479         */
1480        private boolean checkGeronimoVersion(String gerVersion) throws IllegalStateException {
1481            String version = serverInfo.getVersion();
1482    
1483            if ((gerVersion == null) || gerVersion.equals("")) {
1484                log.error("geronimo-version cannot be empty.");
1485                throw new IllegalStateException("geronimo-version cannot be empty.");
1486            } else {
1487                return gerVersion.equals(version);
1488            }
1489        }
1490    
1491        public static void addGeronimoDependencies(ConfigurationData data, List<DependencyType> deps, boolean includeVersion) {
1492            processDependencyList(data.getEnvironment().getDependencies(), deps, includeVersion);
1493            Map<String, ConfigurationData> children = data.getChildConfigurations();
1494            for (ConfigurationData child : children.values()) {
1495                processDependencyList(child.getEnvironment().getDependencies(), deps, includeVersion);
1496            }
1497        }
1498    
1499        /**
1500         * Generates dependencies and an optional prerequisite based on a list of
1501         * dependencies for a Gernonimo module.
1502         *
1503         * @param real   A list with elements of type Dependency
1504         * @param deps   A list with elements of type String (holding a module ID / Artifact name)
1505         * @param includeVersion whether to include a version in the plugin xml dependency
1506         */
1507        private static void processDependencyList(List<Dependency> real, List<DependencyType> deps, boolean includeVersion) {
1508            for (Dependency dep : real) {
1509                DependencyType dependency = toDependencyType(dep, includeVersion);
1510                if (!deps.contains(dependency)) {
1511                    deps.add(dependency);
1512                }
1513            }
1514        }
1515    
1516        public static DependencyType toDependencyType(Dependency dep, boolean includeVersion) {
1517            Artifact id = dep.getArtifact();
1518            DependencyType dependency = new DependencyType();
1519            dependency.setGroupId(id.getGroupId());
1520            dependency.setArtifactId(id.getArtifactId());
1521            if (includeVersion) {
1522                dependency.setVersion(id.getVersion() == null ? null : id.getVersion().toString());
1523            }
1524            dependency.setType(id.getType());
1525            return dependency;
1526        }
1527    
1528        public static Artifact toArtifact(ArtifactType moduleId) {
1529            String groupId = moduleId.getGroupId();
1530            String artifactId = moduleId.getArtifactId();
1531            String version = moduleId.getVersion();
1532            String type = moduleId.getType();
1533            return new Artifact(groupId, artifactId, version, type);
1534        }
1535    
1536        public static ArtifactType toArtifactType(Artifact id) {
1537            ArtifactType artifact = new ArtifactType();
1538            artifact.setGroupId(id.getGroupId());
1539            artifact.setArtifactId(id.getArtifactId());
1540            artifact.setVersion(id.getVersion() == null ? null : id.getVersion().toString());
1541            artifact.setType(id.getType());
1542            return artifact;
1543        }
1544    
1545        public static PluginType copy(PluginType metadata, PluginArtifactType instance) {
1546            PluginType copy = new PluginType();
1547            copy.setAuthor(metadata.getAuthor());
1548            copy.setCategory(metadata.getCategory());
1549            copy.setDescription(metadata.getDescription());
1550            copy.setName(metadata.getName());
1551            copy.setUrl(metadata.getUrl());
1552            copy.getLicense().addAll(metadata.getLicense());
1553            if (instance != null) {
1554                copy.getPluginArtifact().add(instance);
1555            }
1556            return copy;
1557        }
1558    
1559        public static PluginType toKey(PluginType metadata) {
1560            PluginType copy = new PluginKey();
1561            copy.setAuthor(metadata.getAuthor());
1562            copy.setCategory(metadata.getCategory());
1563            copy.setName(metadata.getName());
1564            copy.setUrl(metadata.getUrl());
1565            copy.getLicense().addAll(metadata.getLicense());
1566            return copy;
1567        }
1568    
1569        private static class PluginKey extends PluginType {
1570            private static final long serialVersionUID = -3864898789387102435L;
1571    
1572            public boolean equals(Object o) {
1573                if (this == o) return true;
1574                if (o == null || getClass() != o.getClass()) return false;
1575    
1576                PluginKey that = (PluginKey) o;
1577    
1578                if (author != null ? !author.equals(that.author) : that.author != null) return false;
1579                if (category != null ? !category.equals(that.category) : that.category != null) return false;
1580                if (name != null ? !name.equals(that.name) : that.name != null) return false;
1581                if (url != null ? !url.equals(that.url) : that.url != null) return false;
1582                if ((license == null) != (that.license == null)) return false;
1583                if (license != null) {
1584                    if (license.size() != that.license.size()) return false;
1585                    int i = 0;
1586                    for (LicenseType licenseType : license) {
1587                        LicenseType otherLicense = that.license.get(i++);
1588                        if (licenseType.isOsiApproved() != otherLicense.isOsiApproved()) return false;
1589                        if (licenseType.getValue() != null ? !licenseType.getValue().equals(otherLicense.getValue()) : otherLicense.getValue() != null) return false;
1590                    }
1591                }
1592    
1593                return true;
1594            }
1595    
1596            public int hashCode() {
1597                int result;
1598                result = (name != null ? name.hashCode() : 0);
1599                result = 31 * result + (category != null ? category.hashCode() : 0);
1600                result = 31 * result + (url != null ? url.hashCode() : 0);
1601                result = 31 * result + (author != null ? author.hashCode() : 0);
1602                return result;
1603            }
1604        }
1605    
1606        public PluginListType createPluginListForRepositories(String repo) throws NoSuchStoreException {
1607            PluginListType pluginList = localSourceRepository.getPluginList();
1608            if (repo != null) {
1609                pluginList.getDefaultRepository().add(repo);
1610            }
1611            return pluginList;
1612        }
1613    
1614    
1615        /**
1616         * If a plugin includes config.xml content, copy it into the attribute
1617         * store.
1618         *
1619         * @param configID     artifact we are installing
1620         * @param pluginData   metadata for plugin
1621         * @param servers      server metadata that might be modified
1622         * @param loadOverride overrides the load setting from plugin metadata in a config.xml module.
1623         * @throws java.io.IOException       if IO problem occurs
1624         * @throws org.apache.geronimo.kernel.InvalidGBeanException
1625         *                                   if an invalid gbean configuration is encountered
1626         * @throws NoServerInstanceException if the plugin expects a server metadata that is not present
1627         */
1628        private void installConfigXMLData(Artifact configID, PluginArtifactType pluginData, Map<String, ServerInstance> servers, boolean loadOverride) throws InvalidGBeanException, IOException, NoServerInstanceException {
1629            if (configManager.isConfiguration(configID)) {
1630                if (pluginData != null && !pluginData.getConfigXmlContent().isEmpty()) {
1631                    for (ConfigXmlContentType configXmlContent : pluginData.getConfigXmlContent()) {
1632                        String serverName = configXmlContent.getServer();
1633                        ServerInstance serverInstance = getServerInstance(serverName, servers);
1634                        serverInstance.getAttributeStore().setModuleGBeans(configID, configXmlContent.getGbean(), loadOverride && configXmlContent.isLoad(), configXmlContent.getCondition());
1635                    }
1636                } else {
1637                    getServerInstance("default", servers).getAttributeStore().setModuleGBeans(configID, null, loadOverride, null);
1638                }
1639            }
1640            if (pluginData == null) {
1641                return;
1642            }
1643            if (!pluginData.getConfigSubstitution().isEmpty()) {
1644                Map<String, Properties> propertiesMap = toPropertiesMap(pluginData.getConfigSubstitution());
1645                for (Map.Entry<String, Properties> entry : propertiesMap.entrySet()) {
1646                    String serverName = entry.getKey();
1647                    ServerInstance serverInstance = getServerInstance(serverName, servers);
1648                    serverInstance.getAttributeStore().addConfigSubstitutions(entry.getValue());
1649                }
1650            }
1651            if (!pluginData.getArtifactAlias().isEmpty()) {
1652                Map<String, Properties> propertiesMap = toPropertiesMap(pluginData.getArtifactAlias());
1653                for (Map.Entry<String, Properties> entry : propertiesMap.entrySet()) {
1654                    String serverName = entry.getKey();
1655                    ServerInstance serverInstance = getServerInstance(serverName, servers);
1656                    serverInstance.getArtifactResolver().addAliases(entry.getValue());
1657                }
1658            }
1659        }
1660    
1661        private ServerInstance getServerInstance(String serverName, Map<String, ServerInstance> servers) throws NoServerInstanceException {
1662            ServerInstance serverInstance = servers.get(serverName);
1663            if (serverInstance == null) {
1664                throw new NoServerInstanceException("No server instance configuration set up for name " + serverName);
1665            }
1666            return serverInstance;
1667        }
1668    
1669        private Map<String, Properties> toPropertiesMap(List<PropertyType> propertyTypes) {
1670            Map<String, Properties> propertiesMap = new HashMap<String, Properties>();
1671            for (PropertyType propertyType : propertyTypes) {
1672                String serverName = propertyType.getServer();
1673                Properties properties = propertiesMap.get(serverName);
1674                if (properties == null) {
1675                    properties = new Properties();
1676                    propertiesMap.put(serverName, properties);
1677                }
1678                properties.setProperty(propertyType.getKey(), propertyType.getValue());
1679            }
1680            return propertiesMap;
1681        }
1682    
1683        /**
1684         * Gets a token unique to this run of the server, used to track asynchronous
1685         * downloads.
1686         *
1687         * @return unique (for this server) key
1688         */
1689        private static Object getNextKey() {
1690            int value;
1691            synchronized (PluginInstallerGBean.class) {
1692                value = ++counter;
1693            }
1694            return value;
1695        }
1696    
1697    
1698        /**
1699         * Helper clas to extract a name and module ID from a plugin metadata file.
1700         */
1701        private static class PluginNameIDHandler extends DefaultHandler {
1702            private String id = "";
1703            private String name = "";
1704            private String element = null;
1705    
1706            public void characters(char ch[], int start, int length) throws SAXException {
1707                if (element != null) {
1708                    if (element.equals("module-id")) {
1709                        id += new String(ch, start, length);
1710                    } else if (element.equals("name")) {
1711                        name += new String(ch, start, length);
1712                    }
1713                }
1714            }
1715    
1716            public void endElement(String uri, String localName, String qName) throws SAXException {
1717                element = null;
1718            }
1719    
1720            public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
1721                if (qName.equals("module-id") || qName.equals("name")) {
1722                    element = qName;
1723                }
1724            }
1725    
1726            public void endDocument() throws SAXException {
1727                id = id.trim();
1728                name = name.trim();
1729            }
1730    
1731            public String getID() {
1732                return id;
1733            }
1734    
1735            public String getName() {
1736                return name;
1737            }
1738    
1739            public boolean isComplete() {
1740                return !id.equals("") && !name.equals("");
1741            }
1742        }
1743    
1744        /**
1745         * Helper class to bridge a FileWriteMonitor to a DownloadPoller.
1746         */
1747        private static class ResultsFileWriteMonitor implements FileWriteMonitor {
1748            private final DownloadPoller results;
1749            private int totalBytes;
1750            private String file;
1751    
1752            public ResultsFileWriteMonitor(DownloadPoller results) {
1753                this.results = results;
1754            }
1755    
1756            public void setTotalBytes(int totalBytes) {
1757                this.totalBytes = totalBytes;
1758            }
1759    
1760            public int getTotalBytes() {
1761                return totalBytes;
1762            }
1763    
1764            public void writeStarted(String fileDescription, int fileSize) {
1765                totalBytes = fileSize;
1766                file = fileDescription;
1767                results.setCurrentFile(fileDescription);
1768                results.setCurrentFilePercent(totalBytes > 0 ? 0 : -1);
1769                results.setCurrentMessage("Downloading " + file);
1770            }
1771    
1772            public void writeProgress(int bytes) {
1773                if (totalBytes > 0) {
1774                    double percent = (double) bytes / (double) totalBytes;
1775                    results.setCurrentFilePercent((int) (percent * 100));
1776                } else {
1777                    results.setCurrentMessage((bytes / 1024) + " kB of " + file);
1778                }
1779            }
1780    
1781            public void writeComplete(int bytes) {
1782                results.setCurrentFilePercent(100);
1783                results.setCurrentMessage("Finished downloading " + file + " (" + (bytes / 1024) + " kB)");
1784                results.addDownloadBytes(bytes);
1785            }
1786    
1787            public DownloadPoller getResults() {
1788                return results;
1789            }
1790        }
1791    
1792        public static final GBeanInfo GBEAN_INFO;
1793    
1794        static {
1795            GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(PluginInstallerGBean.class);
1796            infoFactory.addInterface(PluginInstaller.class);
1797            infoFactory.addReference("ConfigManager", ConfigurationManager.class, "ConfigurationManager");
1798            infoFactory.addReference("Repository", WritableListableRepository.class, "Repository");
1799            infoFactory.addReference("ConfigStore", ConfigurationStore.class, "ConfigurationStore");
1800            infoFactory.addReference("ServerInfo", ServerInfo.class, "GBean");
1801            infoFactory.addReference("ThreadPool", ThreadPool.class, "GBean");
1802            infoFactory.addReference("ServerInstances", ServerInstanceData.class, "ServerInstanceData");
1803            infoFactory.addReference("ArtifactManager", ArtifactManager.class, "GBean");
1804            infoFactory.addReference("PersistentConfigurationLists", PersistentConfigurationList.class, "AttributeStore");
1805            infoFactory.addAttribute("classLoader", ClassLoader.class, false);
1806            infoFactory.setConstructor(new String[]{"ConfigManager", "Repository", "ConfigStore",
1807                    "ServerInstances", "ServerInfo", "ThreadPool", "ArtifactManager", "PersistentConfigurationLists", "classLoader"});
1808    
1809            GBEAN_INFO = infoFactory.getBeanInfo();
1810        }
1811    
1812        public static GBeanInfo getGBeanInfo() {
1813            return GBEAN_INFO;
1814        }
1815    }