Package org.apache.geronimo.system.plugin

Source Code of org.apache.geronimo.system.plugin.PluginInstallerGBean$PluginNameIDHandler

/**
*  Licensed to the Apache Software Foundation (ASF) under one or more
*  contributor license agreements.  See the NOTICE file distributed with
*  this work for additional information regarding copyright ownership.
*  The ASF licenses this file to You under the Apache License, Version 2.0
*  (the "License"); you may not use this file except in compliance with
*  the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing, software
*  distributed under the License is distributed on an "AS IS" BASIS,
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*  See the License for the specific language governing permissions and
*  limitations under the License.
*/
package org.apache.geronimo.system.plugin;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.SortedSet;
import java.util.Stack;
import java.util.TreeSet;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;

import javax.security.auth.login.FailedLoginException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.geronimo.gbean.GBeanInfo;
import org.apache.geronimo.gbean.GBeanInfoBuilder;
import org.apache.geronimo.gbean.ReferenceCollection;
import org.apache.geronimo.gbean.ReferenceCollectionEvent;
import org.apache.geronimo.gbean.ReferenceCollectionListener;
import org.apache.geronimo.kernel.InvalidGBeanException;
import org.apache.geronimo.kernel.Kernel;
import org.apache.geronimo.kernel.basic.BasicKernel;
import org.apache.geronimo.kernel.config.ConfigurationData;
import org.apache.geronimo.kernel.config.ConfigurationManager;
import org.apache.geronimo.kernel.config.ConfigurationStore;
import org.apache.geronimo.kernel.config.InvalidConfigException;
import org.apache.geronimo.kernel.config.KernelConfigurationManager;
import org.apache.geronimo.kernel.config.NoSuchConfigException;
import org.apache.geronimo.kernel.config.NoSuchStoreException;
import org.apache.geronimo.kernel.config.PersistentConfigurationList;
import org.apache.geronimo.kernel.repository.Artifact;
import org.apache.geronimo.kernel.repository.ArtifactManager;
import org.apache.geronimo.kernel.repository.DefaultArtifactManager;
import org.apache.geronimo.kernel.repository.Dependency;
import org.apache.geronimo.kernel.repository.FileWriteMonitor;
import org.apache.geronimo.kernel.repository.ImportType;
import org.apache.geronimo.kernel.repository.MissingDependencyException;
import org.apache.geronimo.kernel.repository.Repository;
import org.apache.geronimo.kernel.repository.Version;
import org.apache.geronimo.kernel.repository.WritableListableRepository;
import org.apache.geronimo.kernel.util.XmlUtil;
import org.apache.geronimo.system.configuration.ConfigurationStoreUtil;
import org.apache.geronimo.system.configuration.RepositoryConfigurationStore;
import org.apache.geronimo.system.configuration.PluginAttributeStore;
import org.apache.geronimo.system.plugin.model.ArtifactType;
import org.apache.geronimo.system.plugin.model.ConfigXmlContentType;
import org.apache.geronimo.system.plugin.model.CopyFileType;
import org.apache.geronimo.system.plugin.model.DependencyType;
import org.apache.geronimo.system.plugin.model.HashType;
import org.apache.geronimo.system.plugin.model.LicenseType;
import org.apache.geronimo.system.plugin.model.PluginArtifactType;
import org.apache.geronimo.system.plugin.model.PluginListType;
import org.apache.geronimo.system.plugin.model.PluginType;
import org.apache.geronimo.system.plugin.model.PrerequisiteType;
import org.apache.geronimo.system.plugin.model.PropertyType;
import org.apache.geronimo.system.plugin.model.AttributesType;
import org.apache.geronimo.system.plugin.model.ModuleType;
import org.apache.geronimo.system.repository.Maven2Repository;
import org.apache.geronimo.system.serverinfo.BasicServerInfo;
import org.apache.geronimo.system.serverinfo.ServerInfo;
import org.apache.geronimo.system.threads.ThreadPool;
import org.codehaus.plexus.util.FileUtils;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
* A GBean that knows how to download configurations from a Maven repository.
*
* @version $Rev: 780371 $ $Date: 2009-05-31 02:38:33 -0400 (Sun, 31 May 2009) $
*/
public class PluginInstallerGBean implements PluginInstaller {
    private final static Log log = LogFactory.getLog(PluginInstallerGBean.class);


    private static int counter;
    private final ConfigurationManager configManager;
    private final GeronimoSourceRepository localSourceRepository;
    private final WritableListableRepository writeableRepo;
    private final ConfigurationStore configStore;
    private final ServerInfo serverInfo;
    private final Map<Object, DownloadResults> asyncKeys;
    private final ThreadPool threadPool;
    private final Collection<? extends ServerInstanceData> serverInstanceDatas;
    private final ClassLoader classLoader;
    private final Map<String, ServerInstance> servers = new HashMap<String, ServerInstance>();
    private final Collection<PersistentConfigurationList> persistentConfigurationLists;

    // This regular expression for repository filename is taken from Maven1Repository.MAVEN_1_PATTERN
    private static final Pattern MAVEN_1_PATTERN_PART = Pattern.compile("(.+)-([0-9].+)\\.([^0-9]+)");

    /**
     * GBean constructor.  Supply an existing ConfigurationManager.  Use for adding to the current server.
     *
     * @param configManager                Configuration Manager for this server
     * @param repository                   repository to install into
     * @param configStore                  configuration store to install into
     * @param serverInstanceDatas          set of server "layouts" to install config info into
     * @param serverInfo                   location of server
     * @param threadPool                   thread pool for async operations
     * @param artifactManager              artifact manager to resolve existing artifacts
     * @param persistentConfigurationLists used to start new plugins in a running server
     * @param classLoader                  classLoader @throws IOException exception if server instance cannot be loaded
     * @throws java.io.IOException from bad ServerInstance
     */
    public PluginInstallerGBean(ConfigurationManager configManager,
                                WritableListableRepository repository,
                                ConfigurationStore configStore,
                                Collection<? extends ServerInstanceData> serverInstanceDatas,
                                final ServerInfo serverInfo,
                                ThreadPool threadPool,
                                final ArtifactManager artifactManager,
                                Collection<PersistentConfigurationList> persistentConfigurationLists,
                                final ClassLoader classLoader) throws IOException {
        this.writeableRepo = repository;
        this.configStore = configStore;
        this.serverInfo = serverInfo;
        this.threadPool = threadPool;
        asyncKeys = Collections.synchronizedMap(new HashMap<Object, DownloadResults>());
        this.serverInstanceDatas = serverInstanceDatas;
        this.persistentConfigurationLists = persistentConfigurationLists == null ? Collections.<PersistentConfigurationList>emptyList() : persistentConfigurationLists;
        this.classLoader = classLoader;
        if (configManager == null) {
            throw new IllegalArgumentException("No default server instance set up");
        }
        this.configManager = configManager;
        localSourceRepository = new GeronimoSourceRepository(configManager.getRepositories(), configManager.getArtifactResolver());
        setUpServerInstances(serverInstanceDatas, serverInfo, artifactManager, servers, writeableRepo, true);
    }

    /**
     * Constructor for use in assembling a new server.
     *
     * @param serverInstanceDatas  set of server layouts
     * @param kernel               kernel for current server
     * @param classLoader          classLoader
     * @param targetRepositoryPath location of repo to install into (not in current server)
     * @param targetServerPath     location of server to install into (not current server
     * @throws IOException if layouts can't be loaded
     */
    public PluginInstallerGBean(String targetRepositoryPath,
                                String targetServerPath,
                                Collection<? extends ServerInstanceData> serverInstanceDatas,
                                final Kernel kernel,
                                final ClassLoader classLoader) throws Exception {
        final ArtifactManager artifactManager = new DefaultArtifactManager();

        FileUtils.forceMkdir(new File(targetServerPath));
        serverInfo = new BasicServerInfo(targetServerPath, false);
        File targetRepositoryFile = serverInfo.resolve(targetRepositoryPath);
        FileUtils.forceMkdir(targetRepositoryFile);
        writeableRepo = new Maven2Repository(targetRepositoryFile);
        configStore = new RepositoryConfigurationStore(writeableRepo);
        threadPool = null;
        asyncKeys = Collections.synchronizedMap(new HashMap<Object, DownloadResults>());
        this.serverInstanceDatas = serverInstanceDatas;
        this.persistentConfigurationLists = Collections.emptyList();
        this.classLoader = classLoader;
        setUpServerInstances(serverInstanceDatas, serverInfo, artifactManager, servers, writeableRepo, false);
        this.configManager = buildConfigurationManager(artifactManager, writeableRepo, kernel, configStore, classLoader, servers);
        localSourceRepository = new GeronimoSourceRepository(configManager.getRepositories(), configManager.getArtifactResolver());
    }

    private static void setUpServerInstances(Collection<? extends ServerInstanceData> serverInstanceDatas,
                                             final ServerInfo serverInfo, final ArtifactManager artifactManager,
                                             final Map<String, ServerInstance> servers,
                                             final WritableListableRepository writeableRepo,
                                             final boolean live) throws IOException {
        List<ServerInstanceData> datas = new ArrayList<ServerInstanceData>(serverInstanceDatas);
        boolean shrank = true;
        while (shrank) {
            shrank = false;
            for (Iterator<ServerInstanceData> it = datas.iterator(); it.hasNext();) {
                ServerInstanceData instance = it.next();
                String dependsOn = instance.getAttributeManagerFrom();
                if (dependsOn == null || servers.containsKey(dependsOn)) {
                    addServerInstance(instance, artifactManager, writeableRepo, serverInfo, servers, live);
                    it.remove();
                    shrank = true;
                }
            }
        }
        if (!datas.isEmpty()) {
            throw new IllegalStateException("Cannot resolve ServerInstanceDatas: " + datas);
        }
        if (serverInstanceDatas instanceof ReferenceCollection) {
            ((ReferenceCollection) serverInstanceDatas).addReferenceCollectionListener(new ReferenceCollectionListener() {

                public void memberAdded(ReferenceCollectionEvent event) {
                    ServerInstanceData instance = (ServerInstanceData) event.getMember();
                    try {
                        addServerInstance(instance, artifactManager, writeableRepo, serverInfo, servers, live);
                    } catch (IOException e) {
                        //nothing to do?? log???
                    }
                }

                public void memberRemoved(ReferenceCollectionEvent event) {
                    ServerInstanceData instance = (ServerInstanceData) event.getMember();
                    servers.remove(instance.getName());
                }
            });
        }
    }

    private static void addServerInstance(ServerInstanceData serverInstance,
                                          ArtifactManager artifactManager,
                                          WritableListableRepository targetRepo,
                                          ServerInfo serverInfo,
                                          Map<String, org.apache.geronimo.system.plugin.ServerInstance> servers,
                                          boolean live) throws IOException {
        File targetConfigDirectory = serverInfo.resolveServer(serverInstance.getConfigFile()).getParentFile();
        FileUtils.forceMkdir(targetConfigDirectory);
        org.apache.geronimo.system.plugin.ServerInstance instance = serverInstance.getServerInstance(artifactManager, targetRepo, serverInfo, servers, live);
        servers.put(instance.getServerName(), instance);
    }

    private static ConfigurationManager buildConfigurationManager(ArtifactManager artifactManager,
                                                                  WritableListableRepository targetRepo,
                                                                  Kernel kernel,
                                                                  ConfigurationStore targetStore,
                                                                  ClassLoader classloader,
                                                                  Map<String, org.apache.geronimo.system.plugin.ServerInstance> servers) throws IOException {
        for (ServerInstance instance : servers.values()) {
            if ("default".equals(instance.getServerName())) {
                KernelConfigurationManager configurationManager = new KernelConfigurationManager(kernel,
                        Collections.singleton(targetStore),
                        instance.getAttributeStore(),
                        (PersistentConfigurationList) instance.getAttributeStore(),
                        artifactManager,
                        instance.getArtifactResolver(),
                        Collections.singleton(targetRepo),
                        null,
                        classloader);
                configurationManager.setOnline(false);
                return configurationManager;
            }
        }
        throw new IllegalStateException("No default server instance found: " + servers.keySet());
    }

    /* now for tests only */
    PluginInstallerGBean(ConfigurationManager configManager, WritableListableRepository repository, ConfigurationStore configStore, ServerInfo serverInfo, ThreadPool threadPool, Collection<ServerInstance> servers) {
        this.configManager = configManager;
        localSourceRepository = new GeronimoSourceRepository(configManager.getRepositories(), configManager.getArtifactResolver());
        this.writeableRepo = repository;
        this.configStore = configStore;
        this.serverInfo = serverInfo;
        this.threadPool = threadPool;
        asyncKeys = Collections.synchronizedMap(new HashMap<Object, DownloadResults>());
        serverInstanceDatas = null;
        this.persistentConfigurationLists = Collections.emptyList();
        classLoader = null;
        for (ServerInstance instance : servers) {
            this.servers.put(instance.getServerName(), instance);
        }
        if (servers instanceof ReferenceCollection) {
            ((ReferenceCollection) servers).addReferenceCollectionListener(new ReferenceCollectionListener() {

                public void memberAdded(ReferenceCollectionEvent event) {
                    ServerInstance instance = (ServerInstance) event.getMember();
                    PluginInstallerGBean.this.servers.put(instance.getServerName(), instance);
                }

                public void memberRemoved(ReferenceCollectionEvent event) {
                    ServerInstance instance = (ServerInstance) event.getMember();
                    PluginInstallerGBean.this.servers.remove(instance.getServerName());
                }
            });
        }
    }

    /**
     * This more or less clones the current PluginInstallerGBean to create one with the same server instances (structure) but using
     * the current server as config store and assembling a server in a provided location.
     *
     * @param targetRepositoryPath     location of repository in new server (normally "repository")
     * @param relativeTargetServerPath Location of server to assemble relative to current server
     * @param pluginList               list of plugins to install
     * @throws Exception if something goes wrong
     */
    public DownloadResults installPluginList(String targetRepositoryPath, String relativeTargetServerPath, PluginListType pluginList) throws Exception {
        DownloadResults downloadPoller = new DownloadResults();
        File targetServerPath = serverInfo.resolveServer(relativeTargetServerPath);
        if (targetServerPath.exists()) {
          FileUtils.forceDelete(targetServerPath);
        }
        String targetServerPathName = targetServerPath.getAbsolutePath();
        Kernel kernel = new BasicKernel("assembly");

        try {
//            kernel.boot();
            PluginInstallerGBean installer = new PluginInstallerGBean(
                    targetRepositoryPath,
                    targetServerPathName,
                    serverInstanceDatas,
                    kernel,
                    classLoader);

            installer.install(pluginList, localSourceRepository, true, null, null, downloadPoller);
        } finally {
            kernel.shutdown();
        }
        return downloadPoller;
    }

    public void mergeOverrides(String server, AttributesType overrides) throws InvalidGBeanException, IOException {
        ServerInstance serverInstance = servers.get(server);
        if (serverInstance == null) {
            throw new NullPointerException("No such server: " + server + ", known servers: " + servers.keySet());
        }
        PluginAttributeStore attributeStore = serverInstance.getAttributeStore();
        for (ModuleType module: overrides.getModule()) {
            Artifact artifact = Artifact.create(module.getName());
            attributeStore.setModuleGBeans(artifact, module.getGbean(), module.isLoad(), module.getCondition());
            attributeStore.save();
        }
        if (overrides.getConfiguration().size() > 0) {
            throw new UnsupportedOperationException("Use modules, not configurations");
        }
    }


    /**
     * Lists the plugins installed in the local Geronimo server, by name and
     * ID.
     *
     * @return A Map with key type String (plugin name) and value type Artifact
     *         (config ID of the plugin).
     */
    public Map getInstalledPlugins() {
        SortedSet<Artifact> artifacts = writeableRepo.list();

        Map<String, Artifact> plugins = new HashMap<String, Artifact>();
        for (Artifact configId : artifacts) {
            File dir = writeableRepo.getLocation(configId);
            if (dir.isDirectory()) {
                File meta = new File(dir, "META-INF");
                if (!meta.isDirectory() || !meta.canRead()) {
                    continue;
                }
                File xml = new File(meta, "geronimo-plugin.xml");
                if (!xml.isFile() || !xml.canRead() || xml.length() == 0) {
                    continue;
                }
                readNameAndID(xml, plugins);
            } else {
                if (!dir.isFile() || !dir.canRead()) {
                    log.error("Cannot read artifact dir " + dir.getAbsolutePath());
                    throw new IllegalStateException("Cannot read artifact dir " + dir.getAbsolutePath());
                }
                try {
                    JarFile jar = new JarFile(dir);
                    try {
                        ZipEntry entry = jar.getEntry("META-INF/geronimo-plugin.xml");
                        if (entry == null) {
                            continue;
                        }
                        InputStream in = jar.getInputStream(entry);
                        readNameAndID(in, plugins);
                        in.close();
                    } finally {
                        jar.close();
                    }
                } catch (IOException e) {
                    log.error("Unable to read JAR file " + dir.getAbsolutePath(), e);
                }
            }
        }
        return plugins;
    }

    /**
     * Gets a ConfigurationMetadata for a configuration installed in the local
     * server.  Should load a saved one if available, or else create a new
     * default one to the best of its abilities.
     *
     * @param moduleId Identifies the configuration.  This must match a
     *                 configuration currently installed in the local server.
     *                 The configId must be fully resolved (isResolved() == true)
     */
    public PluginType getPluginMetadata(Artifact moduleId) {
        PluginType type = localSourceRepository.extractPluginMetadata(moduleId);
        if (null == type) {
            try {
                type = createDefaultMetadata(moduleId);
            } catch (InvalidConfigException e) {
                log.warn("Unable to generate metadata for " + moduleId, e);
            } catch (Exception e) {
                log.warn("Error generating metadata for " + moduleId, e);
            }
        }
        return type;
    }

    /**
     * Saves a ConfigurationMetadata for a particular plugin, if the server is
     * able to record it.  This can be used if you later re-export the plugin,
     * or just want to review the information for a particular installed
     * plugin.
     *
     * @param metadata The data to save.  The contained configId (which must
     *                 be fully resolved) identifies the configuration to save
     *                 this for.
     */
    public void updatePluginMetadata(PluginType metadata) {
        PluginArtifactType instance = metadata.getPluginArtifact().get(0);
        Artifact artifact = toArtifact(instance.getModuleId());
        File dir = writeableRepo.getLocation(artifact);
        if (dir == null) {
            log.error(artifact + " is not installed.");
            throw new IllegalArgumentException(artifact + " is not installed.");
        }
        if (!dir.isDirectory()) { // must be a packed (JAR-formatted) plugin
            try {
                File temp = new File(dir.getParentFile(), dir.getName() + ".temp");
                JarFile input = new JarFile(dir);
                Manifest manifest = input.getManifest();
                JarOutputStream out = manifest == null ? new JarOutputStream(
                        new BufferedOutputStream(new FileOutputStream(temp)))
                        : new JarOutputStream(new BufferedOutputStream(new FileOutputStream(temp)), manifest);
                Enumeration en = input.entries();
                byte[] buf = new byte[4096];
                int count;
                while (en.hasMoreElements()) {
                    JarEntry entry = (JarEntry) en.nextElement();
                    if (entry.getName().equals("META-INF/geronimo-plugin.xml")) {
                        entry = new JarEntry(entry.getName());
                        out.putNextEntry(entry);
                        PluginXmlUtil.writePluginMetadata(metadata, out);
                    } else if (entry.getName().equals("META-INF/MANIFEST.MF")) {
                        // do nothing, already passed in a manifest
                    } else {
                        out.putNextEntry(entry);
                        InputStream in = input.getInputStream(entry);
                        while ((count = in.read(buf)) > -1) {
                            out.write(buf, 0, count);
                        }
                        in.close();
                        out.closeEntry();
                    }
                }
                out.flush();
                out.close();
                input.close();
                if (!dir.delete()) {
                    log.error("Unable to delete old plugin at " + dir.getAbsolutePath());
                    throw new IOException("Unable to delete old plugin at " + dir.getAbsolutePath());
                }
                if (!temp.renameTo(dir)) {
                    log.error("Unable to move new plugin " + temp.getAbsolutePath() + " to " + dir.getAbsolutePath());
                    throw new IOException(
                            "Unable to move new plugin " + temp.getAbsolutePath() + " to " + dir.getAbsolutePath());
                }
            } catch (Exception e) {
                log.error("Unable to update plugin metadata", e);
                throw new RuntimeException("Unable to update plugin metadata", e);
            } // TODO this really should have a finally block to ensure streams are closed
        } else {
            File meta = new File(dir, "META-INF");
            if (!meta.isDirectory() || !meta.canRead()) {
                log.error(artifact + " is not a plugin.");
                throw new IllegalArgumentException(artifact + " is not a plugin.");
            }
            File xml = new File(meta, "geronimo-plugin.xml");
            FileOutputStream fos = null;
            try {
                if (!xml.isFile()) {
                    if (!xml.createNewFile()) {
                        log.error("Cannot create plugin metadata file for " + artifact);
                        throw new RuntimeException("Cannot create plugin metadata file for " + artifact);
                    }
                }
                fos = new FileOutputStream(xml);
                PluginXmlUtil.writePluginMetadata(metadata, fos);
            } catch (Exception e) {
                log.error("Unable to save plugin metadata for " + artifact, e);
            } finally {
                if (fos != null) {
                    try {
                        fos.close();
                    } catch (IOException ignored) {
                        // ignored
                    }
                }
            }
        }
    }

    /**
     * Lists the plugins available for download in a particular Geronimo repository.
     *
     * @param mavenRepository The base URL to the maven repository.  This must
     *                        contain the file geronimo-plugins.xml
     * @param username        Optional username, if the maven repo uses HTTP Basic authentication.
     *                        Set this to null if no authentication is required.
     * @param password        Optional password, if the maven repo uses HTTP Basic authentication.
     *                        Set this to null if no authentication is required.
     */
    public PluginListType listPlugins(URL mavenRepository, String username, String password) throws IOException, FailedLoginException {
        try {
            SourceRepository repo = SourceRepositoryFactory.getSourceRepository(mavenRepository.toString(), username, password);
            return repo.getPluginList();
        } catch (IllegalStateException e) {
            return null;
        }
    }

    private SourceRepository getDefaultSourceRepository(String defaultRepository,
                                                        boolean restrictToDefaultRepository) {
        if (restrictToDefaultRepository && defaultRepository == null) {
            throw new IllegalArgumentException("You must supply a default repository if you want to restrict to it");
        }
        SourceRepository defaultSourceRepository = defaultRepository == null ? null : SourceRepositoryFactory.getSourceRepository(defaultRepository);
        return defaultSourceRepository;
    }
   
    /**
     * Installs a configuration from a remote repository into the local Geronimo server,
     * including all its dependencies.  The caller will get the results when the
     * operation completes.  Note that this method does not throw exceptions on failure,
     * but instead sets the failure property of the DownloadResults.
     *
     * @param pluginsToInstall            The list of configurations to install
     * @param defaultRepository           Default repo to look for plugins in
     * @param restrictToDefaultRepository Whether to follow hints to other plugin repos.
     * @param username                    Optional username, if the maven repo uses HTTP Basic authentication.
     *                                    Set this to null if no authentication is required.
     * @param password                    Optional password, if the maven repo uses HTTP Basic authentication.
     *                                    Set this to null if no authentication is required.
     */
    public DownloadResults install(PluginListType pluginsToInstall, String defaultRepository, boolean restrictToDefaultRepository, String username, String password) {
        DownloadResults results = new DownloadResults();
        install(pluginsToInstall, defaultRepository, restrictToDefaultRepository, username, password, results);
        return results;
    }

    /**
     * Installs a configuration from a remote repository into the local Geronimo server,
     * including all its dependencies.  The method blocks until the operation completes,
     * but the caller will be notified of progress frequently along the way (using the
     * supplied DownloadPoller).  Therefore the caller is meant to create the poller and
     * then call this method in a background thread.  Note that this method does not
     * throw exceptions on failure, but instead sets the failure property of the
     * DownloadPoller.
     *
     * @param pluginsToInstall            The list of configurations to install
     * @param defaultRepository           Default repo to look for plugins in (not required)
     * @param restrictToDefaultRepository Whether to follow hints to other plugin repos.
     * @param username                    Optional username, if the maven repo uses HTTP Basic authentication.
     *                                    Set this to null if no authentication is required.
     * @param password                    Optional password, if the maven repo uses HTTP Basic authentication.
     *                                    Set this to null if no authentication is required.
     * @param poller                      Will be notified with status updates as the download proceeds
     */
    public void install(PluginListType pluginsToInstall, String defaultRepository, boolean restrictToDefaultRepository, String username, String password, DownloadPoller poller) {
        SourceRepository defaultSourceRepository = getDefaultSourceRepository(defaultRepository, restrictToDefaultRepository);
        install(pluginsToInstall, defaultSourceRepository, restrictToDefaultRepository, username, password, poller);
    }

    public void install(PluginListType pluginsToInstall, SourceRepository defaultRepository, boolean restrictToDefaultRepository, String username, String password, DownloadPoller poller) {
        install(pluginsToInstall, defaultRepository, restrictToDefaultRepository, username, password, poller, true);
    }
   
    public void install(PluginListType pluginsToInstall, SourceRepository defaultRepository, boolean restrictToDefaultRepository, String username, String password, DownloadPoller poller, boolean validatePlugins) {
        List<Artifact> downloadedArtifacts = new ArrayList<Artifact>();
        try {
            Map<Artifact, PluginType> metaMap = new HashMap<Artifact, PluginType>();
            // Step 1: validate everything
            List<PluginType> toInstall = new ArrayList<PluginType>();
            for (PluginType metadata : pluginsToInstall.getPlugin()) {
                try {
                    if (validatePlugins) {
                        validatePlugin(metadata);
                        verifyPrerequisites(metadata);
                    }

                    PluginArtifactType instance = metadata.getPluginArtifact().get(0);

                    if (instance.getModuleId() != null) {
                        metaMap.put(toArtifact(instance.getModuleId()), metadata);
                    }
                    toInstall.add(metadata);
                } catch (MissingDependencyException e) {
                    poller.addSkippedConfigID(e);
                }
            }

            // Step 2: everything is valid, do the installation
            for (PluginType metadata : toInstall) {
                // 2. Unload obsoleted configurations
                PluginArtifactType instance = metadata.getPluginArtifact().get(0);
                List<Artifact> obsoletes = new ArrayList<Artifact>();
                for (ArtifactType obs : instance.getObsoletes()) {
                    Artifact obsolete = toArtifact(obs);
                    Artifact[] list = configManager.getArtifactResolver().queryArtifacts(obsolete);
                    for (Artifact artifact : list) {
                        if (configManager.isLoaded(artifact)) {
                            if (configManager.isRunning(artifact)) {
                                configManager.stopConfiguration(artifact);
                            }
                            configManager.unloadConfiguration(artifact);
                            obsoletes.add(artifact);
                        }
                    }
                }
                // 3. Download the artifact if necessary, and its dependencies
                Set<Artifact> working = new HashSet<Artifact>();
                Stack<Artifact> parentStack = new Stack<Artifact>();
                if (instance.getModuleId() != null) {
                    Artifact entry = toArtifact(instance.getModuleId());
                    List<SourceRepository> repos = getRepos(pluginsToInstall, defaultRepository, restrictToDefaultRepository, instance);
                    downloadArtifact(entry, metaMap, repos,
                            username, password, new ResultsFileWriteMonitor(poller), working, parentStack, false, servers, true);
                    downloadedArtifacts.add(entry);
                } else {
                    List<DependencyType> deps = instance.getDependency();
                    for (DependencyType dep : deps) {
                        Artifact entry = toArtifact(dep);
                        List<SourceRepository> repos = getRepos(pluginsToInstall, defaultRepository, restrictToDefaultRepository, instance);
                        downloadArtifact(entry, metaMap, repos,
                                username, password, new ResultsFileWriteMonitor(poller), working, parentStack, false, servers, dep.isStart());
                        downloadedArtifacts.add(entry);
                    }
                }
                // 4. Uninstall obsolete configurations
                for (Artifact artifact : obsoletes) {
                    configManager.uninstallConfiguration(artifact);
                }
                // 5. Installation of this configuration finished successfully
            }

            // Step 3: Start anything that's marked accordingly
            if (configManager.isOnline()) {
                poller.setCurrentFilePercent(-1);
                for (PersistentConfigurationList persistentConfigurationList : persistentConfigurationLists) {
                    List<Artifact> artifacts = persistentConfigurationList.restore();
                    for (Artifact artifact : artifacts) {
                        if (!configManager.isRunning(artifact)) {
                            poller.setCurrentMessage("Starting " + artifact);
              if (!configManager.isLoaded(artifact)) {
                                try {
                                    configManager.loadConfiguration(artifact);
                                } catch (Exception e) {
                                    log.error("Unable to load configuration. ", e);
                                    configManager.uninstallConfiguration(artifact);
                                }
                            }
                            if (configManager.isLoaded(artifact)) {
                                configManager.startConfiguration(artifact);
                            }
                        }
                    }
                }
            }
            //ensure config.xml is saved.
            for (org.apache.geronimo.system.plugin.ServerInstance serverInstance : servers.values()) {
                serverInstance.getAttributeStore().save();
            }
        } catch (Exception e) {
            log.error("Unable to install plugin. ", e);
            poller.setFailure(e);
            //Attempt to cleanup a failed plugin installation
            for (Artifact artifact : downloadedArtifacts) {
                try {
                    configManager.uninstallConfiguration(artifact);
                } catch (Exception e2) {
                    log.warn("Warning: ",e2);
                }
            }
        } finally {
            poller.setFinished();
        }
    }

    private List<SourceRepository> getRepos(PluginListType pluginsToInstall, SourceRepository defaultRepository, boolean restrictToDefaultRepository, PluginArtifactType instance) {
        List<SourceRepository> repos = new ArrayList<SourceRepository>();
        if (defaultRepository != null) {
            repos.add(defaultRepository);
        }
        if (!restrictToDefaultRepository) {
            if (!instance.getSourceRepository().isEmpty()) {
                addRepos(repos, instance.getSourceRepository());
            }
           
            //always add the default repository location no matter if the plugin instance contains source-repository.
            addRepos(repos, pluginsToInstall.getDefaultRepository());
        }
        return repos;
    }

    private void addRepos(List<SourceRepository> repos, List<String> repoLocations) {
        for (String repoLocation : repoLocations) {
            try {
                SourceRepository repo = SourceRepositoryFactory.getSourceRepository(repoLocation);
                repos.add(repo);
            } catch (IllegalStateException e) {
                log.warn("Invalid repository: " + repoLocation, e);
            }
        }
    }

    /**
     * Installs a configuration from a remote repository into the local Geronimo server,
     * including all its dependencies.  The method returns immediately, providing a key
     * that can be used to poll the status of the download operation.  Note that the
     * installation does not throw exceptions on failure, but instead sets the failure
     * property of the DownloadResults that the caller can poll for.
     *
     * @param pluginsToInstall            The list of configurations to install
     * @param defaultRepository           Default repo to look for plugins in
     * @param restrictToDefaultRepository Whether to follow hints to other plugin repos.
     * @param username                    Optional username, if the maven repo uses HTTP Basic authentication.
     *                                    Set this to null if no authentication is required.
     * @param password                    Optional password, if the maven repo uses HTTP Basic authentication.
     *                                    Set this to null if no authentication is required. @return A key that can be passed to checkOnInstall
     */
    public Object startInstall(final PluginListType pluginsToInstall, final String defaultRepository, final boolean restrictToDefaultRepository, final String username, final String password) {
        Object key = getNextKey();
        final DownloadResults results = new DownloadResults();
        Runnable work = new Runnable() {
            public void run() {
                install(pluginsToInstall, defaultRepository, restrictToDefaultRepository, username, password, results);
            }
        };
        asyncKeys.put(key, results);
        try {
            threadPool.execute("Configuration Installer", work);
        } catch (InterruptedException e) {
            log.error("Unable to start work", e);
            throw new RuntimeException("Unable to start work", e);
        }
        return key;
    }

    /**
     * Installs a configuration downloaded from a remote repository into the local Geronimo
     * server, including all its dependencies.  The method returns immediately, providing a
     * key that can be used to poll the status of the download operation.  Note that the
     * installation does not throw exceptions on failure, but instead sets the failure
     * property of the DownloadResults that the caller can poll for.
     *
     * @param carFile                     A CAR file downloaded from a remote repository.  This is a packaged
     *                                    configuration with included configuration information, but it may
     *                                    still have external dependencies that need to be downloaded
     *                                    separately.  The metadata in the CAR file includes a repository URL
     *                                    for these downloads, and the username and password arguments are
     *                                    used in conjunction with that.
     * @param defaultRepository           Default repo to look for plugins in
     * @param restrictToDefaultRepository Whether to follow hints to other plugin repos.
     * @param username                    Optional username, if the maven repo uses HTTP Basic authentication.
     *                                    Set this to null if no authentication is required.
     * @param password                    Optional password, if the maven repo uses HTTP Basic authentication.
     *                                    Set this to null if no authentication is required. @return A key that can be passed to checkOnInstall
     */
    public Object startInstall(final File carFile, final String defaultRepository, final boolean restrictToDefaultRepository, final String username, final String password) {
        Object key = getNextKey();
        final DownloadResults results = new DownloadResults();
        Runnable work = new Runnable() {
            public void run() {
                install(carFile, defaultRepository, restrictToDefaultRepository, username, password, results);
            }
        };
        asyncKeys.put(key, results);
        try {
            threadPool.execute("Configuration Installer", work);
        } catch (InterruptedException e) {
            log.error("Unable to start work", e);
            throw new RuntimeException("Unable to start work", e);
        }
        return key;
    }

    /**
     * Gets the current progress of a download operation.  Note that once the
     * DownloadResults is returned for this operation shows isFinished = true,
     * the operation will be forgotten, so the caller should be careful not to
     * call this again after the download has finished.
     *
     * @param key Identifies the operation to check on
     */
    public DownloadResults checkOnInstall(Object key) {
        return checkOnInstall(key, true);
    }
   
    /**
     * Gets the current progress of a download operation. 
     *
     * @param key Identifies the operation to check on
     * @param remove If true and the download operation has finished, the DownloadResults
     *        will be forgotten and the next call to this function will return null.
     *        Otherwise, the DownloadResults will be retained until this function is
     *        called with the <tt>remove</tt> parameter set to true. This parameter is
     *        only used when the download operation has finished
     *        (DownloadResults.isFinished() returns true).
     */
    public DownloadResults checkOnInstall(Object key, boolean remove) {
        DownloadResults results = asyncKeys.get(key);
        if (results != null) {              
            results = results.duplicate();
            if (results.isFinished() && remove) {
                asyncKeys.remove(key);
            }
        }
        return results;
    }

    /**
     * Installs from a pre-downloaded CAR file
     *
     * @param carFile                     care file to install
     * @param defaultRepository           Default repo to look for plugins in
     * @param restrictToDefaultRepository Whether to follow hints to other plugin repos.
     * @param username                    repo username
     * @param password                    repo password
     * @param poller                      monitor for reporting progress
     */
    public void install(File carFile, String defaultRepository, boolean restrictToDefaultRepository, String username, String password, DownloadPoller poller) {
        try {
            // 1. Extract the configuration metadata
            PluginType data = GeronimoSourceRepository.extractPluginMetadata(carFile);
            if (data == null) {
                log.error("Invalid Configuration Archive " + carFile.getAbsolutePath() + " no plugin metadata found");
                throw new IllegalArgumentException(
                        "Invalid Configuration Archive " + carFile.getAbsolutePath() + " no plugin metadata found");
            }

            // 2. Validate that we can install this
            validatePlugin(data);
            verifyPrerequisites(data);
           
            PluginArtifactType instance = data.getPluginArtifact().get(0);
            // 3. Install the CAR into the repository (it shouldn't be re-downloaded)
            if (instance.getModuleId() != null) {
                Artifact pluginArtifact = toArtifact(instance.getModuleId());
                ResultsFileWriteMonitor monitor = new ResultsFileWriteMonitor(poller);
                writeableRepo.copyToRepository(carFile, pluginArtifact, monitor);
                installConfigXMLData(pluginArtifact, instance, servers, true);
                if (instance.getCopyFile() != null) {
                    extractPluginFiles(pluginArtifact, data, monitor);
                }
            }

            // 4. Use the standard logic to remove obsoletes, install dependencies, etc.
            PluginListType pluginList = new PluginListType();
            pluginList.getPlugin().add(data);
            pluginList.getDefaultRepository().addAll(instance.getSourceRepository());
           
            SourceRepository defaultSourceRepository = getDefaultSourceRepository(defaultRepository, restrictToDefaultRepository);
           
            install(pluginList, defaultSourceRepository, restrictToDefaultRepository, username, password, poller, false);
        } catch (Exception e) {
            poller.setFailure(e);
        } finally {
            poller.setFinished();
        }
    }

    /**
     * Ensures that a plugin is installable.
     *
     * @param plugin plugin to check
     * @throws org.apache.geronimo.kernel.repository.MissingDependencyException
     *          if plugin requires a dependency that is not present
     */
    public void validatePlugin(PluginType plugin) throws MissingDependencyException {
        if (plugin.getPluginArtifact().size() != 1) {
            throw new MissingDependencyException("A plugin configuration must include one plugin artifact, not " + plugin.getPluginArtifact().size(), null, (Stack<Artifact>) null);
        }
        PluginArtifactType metadata = plugin.getPluginArtifact().get(0);
        // 1. Check that it's not already installed
        if (metadata.getModuleId() != null) { // that is, it's a real configuration not a plugin list
            Artifact artifact = toArtifact(metadata.getModuleId());
            if (configManager.isInstalled(artifact)) {
                boolean upgrade = false;
                for (ArtifactType obsolete : metadata.getObsoletes()) {
                    Artifact test = toArtifact(obsolete);
                    if (test.matches(artifact)) {
                        upgrade = true;
                        break;
                    }
                }
                if (!upgrade) {
                    log.debug("Configuration " + artifact + " is already installed.");
                    throw new MissingDependencyException(
                            "Configuration " + artifact + " is already installed.", toArtifact(metadata.getModuleId()), (Stack<Artifact>) null);
                }
            }
        }

        // 2. Check that we meet the Geronimo, JVM versions
        if (metadata.getGeronimoVersion().size() > 0 && !checkGeronimoVersions(metadata.getGeronimoVersion())) {
            log.debug("Plugin " + toArtifact(metadata.getModuleId()) + " is not installable on Geronimo " + serverInfo.getVersion());
            throw new MissingDependencyException(
                    "Plugin is not installable on Geronimo " + serverInfo.getVersion(), toArtifact(metadata.getModuleId()), (Stack<Artifact>) null);
        }
        if (metadata.getJvmVersion().size() > 0 && !checkJVMVersions(metadata.getJvmVersion())) {
            log.debug("Plugin " + toArtifact(metadata.getModuleId()) + " is not installable on JVM " + System.getProperty("java.version"));
            throw new MissingDependencyException(
                    "Plugin is not installable on JVM " + System.getProperty("java.version"), toArtifact(metadata.getModuleId()), (Stack<Artifact>) null);
        }
    }


    /**
     * Ensures that a plugin's prerequisites are installed
     *
     * @param plugin plugin artifact to check
     * @return array of missing depedencies
     */
    public Dependency[] checkPrerequisites(PluginType plugin) {
        List<Dependency> missingPrereqs = getMissingPrerequisites(plugin);
        return missingPrereqs.toArray(new Dependency[missingPrereqs.size()]);
    }

    private List<Dependency> getMissingPrerequisites(PluginType plugin) {
        if (plugin.getPluginArtifact().size() != 1) {
            throw new IllegalArgumentException("A plugin configuration must include one plugin artifact, not " + plugin.getPluginArtifact().size());
        }

        PluginArtifactType metadata = plugin.getPluginArtifact().get(0);
        List<PrerequisiteType> prereqs = metadata.getPrerequisite();

        ArrayList<Dependency> missingPrereqs = new ArrayList<Dependency>();
        for (PrerequisiteType prereq : prereqs) {
            Artifact artifact = toArtifact(prereq.getId());
            try {
                if (getServerInstance("default", servers).getArtifactResolver().queryArtifacts(artifact).length == 0) {
                    missingPrereqs.add(new Dependency(artifact, ImportType.ALL));
                }
            } catch (NoServerInstanceException e) {
                throw new RuntimeException("Invalid setup, no default server instance registered");
            }
        }
        return missingPrereqs;
    }

    private void verifyPrerequisites(PluginType plugin) throws MissingDependencyException {
        List<Dependency> missingPrereqs = getMissingPrerequisites(plugin);
        if (!missingPrereqs.isEmpty()) {
            PluginArtifactType metadata = plugin.getPluginArtifact().get(0);
            Artifact moduleId = toArtifact(metadata.getModuleId());
            StringBuffer buf = new StringBuffer();
            buf.append(moduleId.toString()).append(" requires ");
            Iterator<Dependency> iter = missingPrereqs.iterator();
            while (iter.hasNext()) {
                buf.append(iter.next().getArtifact().toString());
                if (iter.hasNext()) {
                    buf.append(", ");
                }
            }
            buf.append(" to be installed");
            throw new MissingDependencyException(buf.toString(), null, (Artifact) null);
        }
    }

    public Artifact installLibrary(File libFile, String groupId) throws IOException {
        Matcher matcher = MAVEN_1_PATTERN_PART.matcher("");
        matcher.reset(libFile.getName());
        if (matcher.matches()) {
            String artifactId = matcher.group(1);
            String version = matcher.group(2);
            String type = matcher.group(3);
            Artifact artifact = new Artifact(groupId != null ? groupId : Artifact.DEFAULT_GROUP_ID, artifactId, version, type);
            writeableRepo.copyToRepository(libFile, artifact, null);
            return artifact;
        } else {
            throw new IllegalArgumentException("Filename " + libFile.getName() + " is not in the form <artifact>-<version>.<type>, for e.g. mylib-1.0.jar.");
        }
    }

    /**
     * Download (if necessary) and install something, which may be a Configuration or may
     * be just a JAR.  For each artifact processed, all its dependencies will be
     * processed as well.
     *
     * @param configID     Identifies the artifact to install
     * @param metadata     name to plugin map
     * @param repos        The URLs to contact the repositories (in order of preference)
     * @param username     The username used for repositories secured with HTTP Basic authentication
     * @param password     The password used for repositories secured with HTTP Basic authentication
     * @param monitor      The ongoing results of the download operations, with some monitoring logic
     * @param soFar        The set of dependencies already downloaded.
     * @param parentStack  chain of modules that led to this dependency
     * @param dependency   Is this a dependency or the original artifact?
     * @param servers      server layouts to install config info into
     * @param loadOverride If false prevents setting load="true" in server instances (recursively through dependencies)
     * @throws FailedLoginException       When a repository requires authentication and either no username
     *                                    and password are supplied or the username and password supplied
     *                                    are not accepted
     * @throws MissingDependencyException When a dependency cannot be located in any of the listed repositories
     * @throws NoServerInstanceException  when no server descriptor is found for a specified configuration bit
     * @throws java.io.IOException        when a IO problem occurs
     */
    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 {
        if (soFar.contains(configID)) {
            return; // Avoid endless work due to circular dependencies
        } else {
            soFar.add(configID);
        }
        // Download and install the main artifact
        boolean pluginWasInstalled = false;
        Artifact[] matches = configManager.getArtifactResolver().queryArtifacts(configID);
        PluginArtifactType instance = null;
        if (matches.length == 0) {
            // not present, needs to be downloaded
            monitor.getResults().setCurrentMessage("Downloading " + configID);
            monitor.getResults().setCurrentFilePercent(-1);
            OpenResult result = null;
            for (SourceRepository repository : repos) {
                result = repository.open(configID, monitor);
                if (result != null) {
                    break;
                }
            }
            if (result == null) {
                throw new IllegalArgumentException("Could not find " + configID + " in any repo");
            }
            // Check if the result is already in server's repository
            if (configManager.getArtifactResolver().queryArtifacts(result.getArtifact()).length > 0) {
                String msg = "Not downloading " + configID + ". Query for " + configID + " resulted in " + result.getArtifact()
                        + " which is already available in server's repository.";
                monitor.getResults().setCurrentMessage(msg);
                log.info(msg);
                result.close();
                return;
            }
            try {
                File tempFile = result.getFile();
                if (tempFile == null) {
                    log.error("Null filehandle was returned for " + configID);
                    throw new IllegalArgumentException("Null filehandle was returned for " + configID);
                }
                PluginType pluginData = metadata.get(configID);
                // Only bother with the hash if we got it from a source other than the download file itself
                HashType hash = pluginData == null ? null : pluginData.getPluginArtifact().get(0).getHash();
                if (hash != null) {
                    String actual = ConfigurationStoreUtil.getActualChecksum(tempFile, hash.getType());
                    if (!actual.equals(hash.getValue())) {
                        log.error(
                                "File download incorrect (expected " + hash.getType() + " hash " + hash.getValue() + " but got " + actual + ")");
                        throw new IOException(
                                "File download incorrect (expected " + hash.getType() + " hash " + hash.getValue() + " but got " + actual + ")");
                    }
                }
                // See if the download file has plugin metadata and use it in preference to what is in the catalog.
                try {
                    PluginType realPluginData = GeronimoSourceRepository.extractPluginMetadata(tempFile);
                    if (realPluginData != null) {
                        pluginData = realPluginData;
                    }
                } catch (Exception e) {
                    log.error("Unable to read plugin metadata: " + e.getMessage());
                    throw (IOException) new IOException(
                            "Unable to read plugin metadata: " + e.getMessage()).initCause(e);
                }
                if (pluginData != null) { // it's a plugin, not a plain JAR
                    validatePlugin(pluginData);
                    instance = pluginData.getPluginArtifact().get(0);
                }
                monitor.getResults().setCurrentMessage("Copying " + result.getArtifact() + " to the repository");
                result.install(writeableRepo, monitor);
                if (pluginData != null) {
                    installConfigXMLData(result.getArtifact(), instance, servers, loadOverride);
                } else {
                    log.debug("No config XML data to install.");
                }
                if (dependency) {
                    monitor.getResults().addDependencyInstalled(configID);
                    configID = result.getArtifact();
                } else {
                    configID = result.getArtifact();
                    monitor.getResults().addInstalledConfigID(configID);
                }
                pluginWasInstalled = true;
                if (pluginData != null)
                    log.info("Installed plugin with moduleId=" + pluginData.getPluginArtifact().get(0).getModuleId() + " and name=" + pluginData.getName());
                else
                    log.info("Installed artifact=" + configID);
            } catch (InvalidGBeanException e) {
                log.error("Invalid gbean configuration ", e);
                throw new IllegalStateException(
                        "Invalid GBean configuration: " + e.getMessage(), e);

            } finally {
                //todo probably not needede
                result.close();
            }
        } else {
            if (dependency) {
                monitor.getResults().addDependencyPresent(configID);
            } else {
                monitor.getResults().addInstalledConfigID(configID);
            }
        }
        // Download and install the dependencies
        try {
            if (!configID.isResolved()) {
                // See if something's running
                for (int i = matches.length - 1; i >= 0; i--) {
                    Artifact match = matches[i];
                    if (configStore.containsConfiguration(match) && configManager.isRunning(match)) {
                        log.debug("Found required configuration=" + match + " and it is running.");
                        return; // its dependencies must be OK
                    } else {
                        log.debug("Either required configuration=" + match + " is not installed or it is not running.");
                    }
                }
                // Go with something that's installed
                configID = matches[matches.length - 1];
            }
            ConfigurationData data = null;
            if (configStore.containsConfiguration(configID)) {
                if (configManager.isRunning(configID)) {
                    return; // its dependencies must be OK
                }
                log.debug("Loading configuration=" + configID);
                data = configStore.loadConfiguration(configID);
            }
            // Download the dependencies
            parentStack.push(configID);
            if (instance == null) {
                //no plugin metadata, guess with something else
                Dependency[] dependencies = data == null ? getDependencies(writeableRepo, configID) : getDependencies(data);
                for (Dependency dep : dependencies) {
                    Artifact artifact = dep.getArtifact();
                    log.debug("Attempting to download dependency=" + artifact + " for configuration=" + configID);
                    downloadArtifact(artifact, metadata, repos, username, password, monitor, soFar, parentStack, true, servers, loadOverride);
                }
            } else {
                //rely on plugin metadata if present.
                List<DependencyType> deps = instance.getDependency();
                for (DependencyType dep : deps) {
                    Artifact artifact = toArtifact(dep);
                    log.debug("Attempting to download dependency=" + artifact + " for configuration=" + configID);
                    downloadArtifact(artifact, metadata, repos, username, password, monitor, soFar, parentStack, true, servers, loadOverride & dep.isStart());
                }
            }
            parentStack.pop();
        } catch (NoSuchConfigException e) {
            log.error("Installed configuration into repository but ConfigStore does not see it: " + e.getMessage());
            throw new IllegalStateException(
                    "Installed configuration into repository but ConfigStore does not see it: " + e.getMessage(), e);
        } catch (InvalidConfigException e) {
            log.error("Installed configuration into repository but ConfigStore cannot load it: " + e.getMessage());
            throw new IllegalStateException(
                    "Installed configuration into repository but ConfigStore cannot load it: " + e.getMessage(), e);
        }
        // Copy any files out of the artifact
        PluginType currentPlugin = getPluginMetadata(configID);
        if (pluginWasInstalled && currentPlugin != null) {
            extractPluginFiles(configID, currentPlugin, monitor);
        }
    }

    private void extractPluginFiles(Artifact configID, PluginType currentPlugin, ResultsFileWriteMonitor monitor) throws IOException {
        PluginArtifactType instance = currentPlugin.getPluginArtifact().get(0);
        for (CopyFileType data : instance.getCopyFile()) {
            monitor.getResults().setCurrentFilePercent(-1);
            monitor.getResults().setCurrentFile(data.getValue());
            monitor.getResults().setCurrentMessage("Copying " + data.getValue() + " from plugin to Geronimo installation");
            copyFile(data, configID);
        }
    }

    void copyFile(CopyFileType data, Artifact configID) throws IOException {
        Set<URL> set;
        String sourceFile = data.getValue().trim();
        try {
            set = configStore.resolve(configID, null, sourceFile);
        } catch (NoSuchConfigException e) {
            log.error("Unable to identify module " + configID + " to copy files from");
            throw new IllegalStateException("Unable to identify module " + configID + " to copy files from", e);
        }
        if (set.size() == 0) {
            log.error("Installed configuration into repository but cannot locate file to copy " + sourceFile);
            return;
        }
        if (set.iterator().next().getPath().endsWith("/")) {
            //directory, get all contents
            String pattern = sourceFile;
            if (pattern.length() == 0 || pattern.endsWith("/")) {
                pattern = pattern + "**";
            } else {
                pattern = pattern + "/**";
            }
            try {
                set = new TreeSet<URL>(new Comparator<URL>() {

                    public int compare(URL o1, URL o2) {
                        return o1.getPath().compareTo(o2.getPath());
                    }
                });
                set.addAll(configStore.resolve(configID, null, pattern));
            } catch (NoSuchConfigException e) {
                log.error("Unable to list directory " + pattern + " to copy files from");
                throw new IllegalStateException("Unable to list directory " + pattern + " to copy files from", e);
            }
        }
        boolean relativeToServer = "server".equals(data.getRelativeTo());
        String destDir = data.getDestDir();
        File targetDir = relativeToServer ? serverInfo.resolveServer(destDir) : serverInfo.resolve(destDir);


        createDirectory(targetDir);
        URI targetURI = targetDir.toURI();
        if (!targetDir.isDirectory()) {
            log.error(
                    "Plugin install cannot write file " + data.getValue() + " to " + destDir + " because " + targetDir.getAbsolutePath() + " is not a directory");
            return;
        }
        if (!targetDir.canWrite()) {
            log.error(
                    "Plugin install cannot write file " + data.getValue() + " to " + destDir + " because " + targetDir.getAbsolutePath() + " is not writable");
            return;
        }
        int start = -1;
        for (URL url : set) {
            String path = url.getPath();
            if (start == -1) {
                if (sourceFile.length() == 0 || sourceFile.endsWith("/")) {
                    if ("jar".equals(url.getProtocol())) {
                        start = path.lastIndexOf("!/") + 2 + sourceFile.length();
                    } else {
                        start = path.length();
                        //this entry needs nothing done
                        continue;
                    }
                } else {
                    String remove = sourceFile;
                    int pos = sourceFile.lastIndexOf('/');
                    if (pos > -1) {
                        remove = sourceFile.substring(pos + 1, sourceFile.length());
                    }
                    start = path.lastIndexOf(remove);
                }
            }
            path = path.substring(start);
            File target = new File(targetURI.resolve(path));
            if (!target.exists()) {
                if (path.endsWith("/")) {
                    if (!target.mkdirs()) {
                        log.error("Plugin install cannot create directory " + target.getAbsolutePath());
                    }
                    continue;
                }
                if (!target.createNewFile()) {
                    log.error("Plugin install cannot create new file " + target.getAbsolutePath());
                    continue;
                }
            }
            if (target.isDirectory()) {
                continue;
            }
            if (!target.canWrite()) {
                log.error("Plugin install cannot write to file " + target.getAbsolutePath());
                continue;
            }
            copyFile(url.openStream(), new FileOutputStream(target));
        }
    }

    private static void createDirectory(File dir) throws IOException {
        if (dir != null && !dir.exists()) {
            boolean success = dir.mkdirs();
            if (!success) {
                throw new IOException("Cannot create directory " + dir.getAbsolutePath());
            }
        }
    }

    private void copyFile(InputStream in, FileOutputStream out) throws IOException {
        byte[] buf = new byte[4096];
        int count;
        while ((count = in.read(buf)) > -1) {
            out.write(buf, 0, count);
        }
        in.close();
        out.flush();
        out.close();
    }

    /**
     * Used to get dependencies for a JAR
     *
     * @param repo     repository containing jar
     * @param artifact artifact to find dependencies of
     * @return dependencies of artifact in repository
     */
    private static Dependency[] getDependencies(Repository repo, Artifact artifact) {
        Set<Artifact> set = repo.getDependencies(artifact);
        Dependency[] results = new Dependency[set.size()];
        int index = 0;
        for (Artifact dep : set) {
            results[index] = new Dependency(dep, ImportType.CLASSES);
            ++index;
        }
        return results;
    }

    /**
     * Used to get dependencies for a Configuration
     *
     * @param data configuration data
     * @return dependencies of configuration
     */
    private static Dependency[] getDependencies(ConfigurationData data) {
        List<Dependency> dependencies = new ArrayList<Dependency>(data.getEnvironment().getDependencies());
        Collection<ConfigurationData> children = data.getChildConfigurations().values();
        for (ConfigurationData child : children) {
            dependencies.addAll(child.getEnvironment().getDependencies());
        }
        return dependencies.toArray(new Dependency[dependencies.size()]);
    }

    /**
     * Searches for an artifact in the listed repositories, where the artifact
     * may have wildcards in the ID.
     */

    /**
     * Puts the name and ID of a plugin into the argument map of plugins,
     * by reading the values out of the provided plugin descriptor file.
     *
     * @param xml     The geronimo-plugin.xml for this plugin
     * @param plugins The result map to populate
     */
    private void readNameAndID(File xml, Map<String, Artifact> plugins) {
        try {
            SAXParserFactory factory = XmlUtil.newSAXParserFactory();
            SAXParser parser = factory.newSAXParser();
            PluginNameIDHandler handler = new PluginNameIDHandler();
            parser.parse(xml, handler);
            if (handler.isComplete()) {
                plugins.put(handler.getName(), Artifact.create(handler.getID()));
            }
        } catch (Exception e) {
            log.warn("Invalid XML at " + xml.getAbsolutePath(), e);
        }
    }

    /**
     * Puts the name and ID of a plugin into the argument map of plugins,
     * by reading the values out of the provided plugin descriptor stream.
     *
     * @param xml     The geronimo-plugin.xml for this plugin
     * @param plugins The result map to populate
     */
    private void readNameAndID(InputStream xml, Map plugins) {
        try {
            SAXParserFactory factory = XmlUtil.newSAXParserFactory();
            SAXParser parser = factory.newSAXParser();
            PluginNameIDHandler handler = new PluginNameIDHandler();
            parser.parse(xml, handler);
            if (handler.isComplete()) {
                plugins.put(handler.getName(), Artifact.create(handler.getID()));
            }
        } catch (Exception e) {
            log.warn("Invalid XML", e);
        }
    }

    /**
     * TODO figure out where this  can be called -- perhaps when installing a plugin?
     * Generates a default plugin metadata based on the data for this module
     * in the server.
     */
    private PluginType createDefaultMetadata(Artifact moduleId) throws InvalidConfigException, IOException, NoSuchConfigException {
        if (configManager != null) {
            if (!configManager.isConfiguration(moduleId)) {
                return null;
            }
        } else {
            if (!configStore.containsConfiguration(moduleId)) {
                return null;
            }
        }
        ConfigurationData data = configStore.loadConfiguration(moduleId);

        PluginType meta = new PluginType();
        PluginArtifactType instance = new PluginArtifactType();
        meta.getPluginArtifact().add(instance);
        meta.setName(toArtifactType(moduleId).getArtifactId());
        instance.setModuleId(toArtifactType(moduleId));
        meta.setCategory("Unknown");
        instance.getGeronimoVersion().add(serverInfo.getVersion());
        instance.getObsoletes().add(toArtifactType(new Artifact(moduleId.getGroupId(),
                moduleId.getArtifactId(),
                (Version) null,
                moduleId.getType())));
        List<DependencyType> deps = instance.getDependency();
        addGeronimoDependencies(data, deps, true);
        return meta;
    }

    /**
     * Check whether the specified JVM versions match the current runtime
     * environment.
     *
     * @param jvmVersions allowed jvm versions
     * @return true if the specified versions match the current
     *         execution environment as defined by plugins-1.2.xsd
     */
    private boolean checkJVMVersions(List<String> jvmVersions) {
        if (jvmVersions.size() == 0) return true;
        String version = System.getProperty("java.version");
        boolean match = false;
        for (String jvmVersion : jvmVersions) {
            if (jvmVersion == null || jvmVersion.equals("")) {
                log.error("jvm-version should not be empty.");
                throw new IllegalStateException("jvm-version should not be empty.");
            }
            if (version.startsWith(jvmVersion)) {
                match = true;
                break;
            }
        }
        return match;
    }

    /**
     * Check whether the specified Geronimo versions match the current runtime
     * environment.
     *
     * @param gerVersions geronimo versions allowed by the plugin
     * @return true if the specified versions match the current
     *         execution environment as defined by plugins-1.2.xsd
     * @throws IllegalStateException if match does not work.
     */
    private boolean checkGeronimoVersions(List<String> gerVersions) throws IllegalStateException {
        if ((gerVersions == null) || (gerVersions.size() == 0)) {
            return true;
        }

        boolean match = false;
        for (String gerVersion : gerVersions) {
            match = checkGeronimoVersion(gerVersion);
            if (match) {
                break;
            }
        }
        return match;
    }

    /**
     * Check whether the specified Geronimo version matches the current runtime
     * environment.
     *
     * @param gerVersion geronimo version to check against this server
     * @return true if the specified version matches the current
     *         execution environment as defined by plugins-1.2.xsd
     * @throws IllegalStateException if input is malformed
     */
    private boolean checkGeronimoVersion(String gerVersion) throws IllegalStateException {
        String version = serverInfo.getVersion();

        if ((gerVersion == null) || gerVersion.equals("")) {
            log.error("geronimo-version cannot be empty.");
            throw new IllegalStateException("geronimo-version cannot be empty.");
        } else {
            return gerVersion.equals(version);
        }
    }

    public static void addGeronimoDependencies(ConfigurationData data, List<DependencyType> deps, boolean includeVersion) {
        processDependencyList(data.getEnvironment().getDependencies(), deps, includeVersion);
        Map<String, ConfigurationData> children = data.getChildConfigurations();
        for (ConfigurationData child : children.values()) {
            processDependencyList(child.getEnvironment().getDependencies(), deps, includeVersion);
        }
    }

    /**
     * Generates dependencies and an optional prerequisite based on a list of
     * dependencies for a Gernonimo module.
     *
     * @param real   A list with elements of type Dependency
     * @param deps   A list with elements of type String (holding a module ID / Artifact name)
     * @param includeVersion whether to include a version in the plugin xml dependency
     */
    private static void processDependencyList(List<Dependency> real, List<DependencyType> deps, boolean includeVersion) {
        for (Dependency dep : real) {
            DependencyType dependency = toDependencyType(dep, includeVersion);
            if (!deps.contains(dependency)) {
                deps.add(dependency);
            }
        }
    }

    public static DependencyType toDependencyType(Dependency dep, boolean includeVersion) {
        Artifact id = dep.getArtifact();
        DependencyType dependency = new DependencyType();
        dependency.setGroupId(id.getGroupId());
        dependency.setArtifactId(id.getArtifactId());
        if (includeVersion) {
            dependency.setVersion(id.getVersion() == null ? null : id.getVersion().toString());
        }
        dependency.setType(id.getType());
        return dependency;
    }

    public static Artifact toArtifact(ArtifactType moduleId) {
        String groupId = moduleId.getGroupId();
        String artifactId = moduleId.getArtifactId();
        String version = moduleId.getVersion();
        String type = moduleId.getType();
        return new Artifact(groupId, artifactId, version, type);
    }

    public static ArtifactType toArtifactType(Artifact id) {
        ArtifactType artifact = new ArtifactType();
        artifact.setGroupId(id.getGroupId());
        artifact.setArtifactId(id.getArtifactId());
        artifact.setVersion(id.getVersion() == null ? null : id.getVersion().toString());
        artifact.setType(id.getType());
        return artifact;
    }

    public static PluginType copy(PluginType metadata, PluginArtifactType instance) {
        PluginType copy = new PluginType();
        copy.setAuthor(metadata.getAuthor());
        copy.setCategory(metadata.getCategory());
        copy.setDescription(metadata.getDescription());
        copy.setName(metadata.getName());
        copy.setUrl(metadata.getUrl());
        copy.getLicense().addAll(metadata.getLicense());
        if (instance != null) {
            copy.getPluginArtifact().add(instance);
        }
        return copy;
    }

    public static PluginType toKey(PluginType metadata) {
        PluginType copy = new PluginKey();
        copy.setAuthor(metadata.getAuthor());
        copy.setCategory(metadata.getCategory());
        copy.setName(metadata.getName());
        copy.setUrl(metadata.getUrl());
        copy.getLicense().addAll(metadata.getLicense());
        return copy;
    }

    private static class PluginKey extends PluginType {
        private static final long serialVersionUID = -3864898789387102435L;

        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            PluginKey that = (PluginKey) o;

            if (author != null ? !author.equals(that.author) : that.author != null) return false;
            if (category != null ? !category.equals(that.category) : that.category != null) return false;
            if (name != null ? !name.equals(that.name) : that.name != null) return false;
            if (url != null ? !url.equals(that.url) : that.url != null) return false;
            if ((license == null) != (that.license == null)) return false;
            if (license != null) {
                if (license.size() != that.license.size()) return false;
                int i = 0;
                for (LicenseType licenseType : license) {
                    LicenseType otherLicense = that.license.get(i++);
                    if (licenseType.isOsiApproved() != otherLicense.isOsiApproved()) return false;
                    if (licenseType.getValue() != null ? !licenseType.getValue().equals(otherLicense.getValue()) : otherLicense.getValue() != null) return false;
                }
            }

            return true;
        }

        public int hashCode() {
            int result;
            result = (name != null ? name.hashCode() : 0);
            result = 31 * result + (category != null ? category.hashCode() : 0);
            result = 31 * result + (url != null ? url.hashCode() : 0);
            result = 31 * result + (author != null ? author.hashCode() : 0);
            return result;
        }
    }

    public PluginListType createPluginListForRepositories(String repo) throws NoSuchStoreException {
        PluginListType pluginList = localSourceRepository.getPluginList();
        if (repo != null) {
            pluginList.getDefaultRepository().add(repo);
        }
        return pluginList;
    }


    /**
     * If a plugin includes config.xml content, copy it into the attribute
     * store.
     *
     * @param configID     artifact we are installing
     * @param pluginData   metadata for plugin
     * @param servers      server metadata that might be modified
     * @param loadOverride overrides the load setting from plugin metadata in a config.xml module.
     * @throws java.io.IOException       if IO problem occurs
     * @throws org.apache.geronimo.kernel.InvalidGBeanException
     *                                   if an invalid gbean configuration is encountered
     * @throws NoServerInstanceException if the plugin expects a server metadata that is not present
     */
    private void installConfigXMLData(Artifact configID, PluginArtifactType pluginData, Map<String, ServerInstance> servers, boolean loadOverride) throws InvalidGBeanException, IOException, NoServerInstanceException {
        if (configManager.isConfiguration(configID)) {
            if (pluginData != null && !pluginData.getConfigXmlContent().isEmpty()) {
                for (ConfigXmlContentType configXmlContent : pluginData.getConfigXmlContent()) {
                    String serverName = configXmlContent.getServer();
                    ServerInstance serverInstance = getServerInstance(serverName, servers);
                    serverInstance.getAttributeStore().setModuleGBeans(configID, configXmlContent.getGbean(), loadOverride && configXmlContent.isLoad(), configXmlContent.getCondition());
                }
            } else {
                getServerInstance("default", servers).getAttributeStore().setModuleGBeans(configID, null, loadOverride, null);
            }
        }
        if (pluginData == null) {
            return;
        }
        if (!pluginData.getConfigSubstitution().isEmpty()) {
            Map<String, Properties> propertiesMap = toPropertiesMap(pluginData.getConfigSubstitution());
            for (Map.Entry<String, Properties> entry : propertiesMap.entrySet()) {
                String serverName = entry.getKey();
                ServerInstance serverInstance = getServerInstance(serverName, servers);
                serverInstance.getAttributeStore().addConfigSubstitutions(entry.getValue());
            }
        }
        if (!pluginData.getArtifactAlias().isEmpty()) {
            Map<String, Properties> propertiesMap = toPropertiesMap(pluginData.getArtifactAlias());
            for (Map.Entry<String, Properties> entry : propertiesMap.entrySet()) {
                String serverName = entry.getKey();
                ServerInstance serverInstance = getServerInstance(serverName, servers);
                serverInstance.getArtifactResolver().addAliases(entry.getValue());
            }
        }
    }

    private ServerInstance getServerInstance(String serverName, Map<String, ServerInstance> servers) throws NoServerInstanceException {
        ServerInstance serverInstance = servers.get(serverName);
        if (serverInstance == null) {
            throw new NoServerInstanceException("No server instance configuration set up for name " + serverName);
        }
        return serverInstance;
    }

    private Map<String, Properties> toPropertiesMap(List<PropertyType> propertyTypes) {
        Map<String, Properties> propertiesMap = new HashMap<String, Properties>();
        for (PropertyType propertyType : propertyTypes) {
            String serverName = propertyType.getServer();
            Properties properties = propertiesMap.get(serverName);
            if (properties == null) {
                properties = new Properties();
                propertiesMap.put(serverName, properties);
            }
            properties.setProperty(propertyType.getKey(), propertyType.getValue());
        }
        return propertiesMap;
    }

    /**
     * Gets a token unique to this run of the server, used to track asynchronous
     * downloads.
     *
     * @return unique (for this server) key
     */
    private static Object getNextKey() {
        int value;
        synchronized (PluginInstallerGBean.class) {
            value = ++counter;
        }
        return value;
    }


    /**
     * Helper clas to extract a name and module ID from a plugin metadata file.
     */
    private static class PluginNameIDHandler extends DefaultHandler {
        private String id = "";
        private String name = "";
        private String element = null;

        public void characters(char ch[], int start, int length) throws SAXException {
            if (element != null) {
                if (element.equals("module-id")) {
                    id += new String(ch, start, length);
                } else if (element.equals("name")) {
                    name += new String(ch, start, length);
                }
            }
        }

        public void endElement(String uri, String localName, String qName) throws SAXException {
            element = null;
        }

        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            if (qName.equals("module-id") || qName.equals("name")) {
                element = qName;
            }
        }

        public void endDocument() throws SAXException {
            id = id.trim();
            name = name.trim();
        }

        public String getID() {
            return id;
        }

        public String getName() {
            return name;
        }

        public boolean isComplete() {
            return !id.equals("") && !name.equals("");
        }
    }

    /**
     * Helper class to bridge a FileWriteMonitor to a DownloadPoller.
     */
    private static class ResultsFileWriteMonitor implements FileWriteMonitor {
        private final DownloadPoller results;
        private int totalBytes;
        private String file;

        public ResultsFileWriteMonitor(DownloadPoller results) {
            this.results = results;
        }

        public void setTotalBytes(int totalBytes) {
            this.totalBytes = totalBytes;
        }

        public int getTotalBytes() {
            return totalBytes;
        }

        public void writeStarted(String fileDescription, int fileSize) {
            totalBytes = fileSize;
            file = fileDescription;
            results.setCurrentFile(fileDescription);
            results.setCurrentFilePercent(totalBytes > 0 ? 0 : -1);
            results.setCurrentMessage("Downloading " + file);
        }

        public void writeProgress(int bytes) {
            if (totalBytes > 0) {
                double percent = (double) bytes / (double) totalBytes;
                results.setCurrentFilePercent((int) (percent * 100));
            } else {
                results.setCurrentMessage((bytes / 1024) + " kB of " + file);
            }
        }

        public void writeComplete(int bytes) {
            results.setCurrentFilePercent(100);
            results.setCurrentMessage("Finished downloading " + file + " (" + (bytes / 1024) + " kB)");
            results.addDownloadBytes(bytes);
        }

        public DownloadPoller getResults() {
            return results;
        }
    }

    public static final GBeanInfo GBEAN_INFO;

    static {
        GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(PluginInstallerGBean.class);
        infoFactory.addInterface(PluginInstaller.class);
        infoFactory.addReference("ConfigManager", ConfigurationManager.class, "ConfigurationManager");
        infoFactory.addReference("Repository", WritableListableRepository.class, "Repository");
        infoFactory.addReference("ConfigStore", ConfigurationStore.class, "ConfigurationStore");
        infoFactory.addReference("ServerInfo", ServerInfo.class, "GBean");
        infoFactory.addReference("ThreadPool", ThreadPool.class, "GBean");
        infoFactory.addReference("ServerInstances", ServerInstanceData.class, "ServerInstanceData");
        infoFactory.addReference("ArtifactManager", ArtifactManager.class, "GBean");
        infoFactory.addReference("PersistentConfigurationLists", PersistentConfigurationList.class, "AttributeStore");
        infoFactory.addAttribute("classLoader", ClassLoader.class, false);
        infoFactory.setConstructor(new String[]{"ConfigManager", "Repository", "ConfigStore",
                "ServerInstances", "ServerInfo", "ThreadPool", "ArtifactManager", "PersistentConfigurationLists", "classLoader"});

        GBEAN_INFO = infoFactory.getBeanInfo();
    }

    public static GBeanInfo getGBeanInfo() {
        return GBEAN_INFO;
    }
}
TOP

Related Classes of org.apache.geronimo.system.plugin.PluginInstallerGBean$PluginNameIDHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
ew');