org.tolven.plugin.repository.RepositoryMetadata.java Source code

Java tutorial

Introduction

Here is the source code for org.tolven.plugin.repository.RepositoryMetadata.java

Source

/*
 * Copyright (C) 2009 Tolven Inc
    
 * This library is free software; you can redistribute it and/or modify it under the terms of 
 * the GNU Lesser General Public License as published by the Free Software Foundation; either 
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;  
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 * See the GNU Lesser General Public License for more details.
 *
 * Contact: info@tolvenhealth.com 
 *
 * @author Joseph Isaac
 * @version $Id$
 */
package org.tolven.plugin.repository;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.tolven.plugin.registry.xml.ManifestParser;
import org.tolven.plugin.registry.xml.ModelPluginFragment;
import org.tolven.plugin.registry.xml.ModelPluginManifest;
import org.tolven.plugin.registry.xml.ModelPrerequisite;
import org.tolven.plugin.registry.xml.bean.Plugin;
import org.tolven.plugin.registry.xml.bean.PluginFragment;
import org.tolven.plugin.repository.bean.ConstraintDetail;
import org.tolven.plugin.repository.bean.DependentPluginDetail;
import org.tolven.plugin.repository.bean.InfoDetail;
import org.tolven.plugin.repository.bean.MessageDigestDetail;
import org.tolven.plugin.repository.bean.ObjectFactory;
import org.tolven.plugin.repository.bean.PluginDetail;
import org.tolven.plugin.repository.bean.PluginPropertyDetail;
import org.tolven.plugin.repository.bean.PluginVersionDetail;
import org.tolven.plugin.repository.bean.Plugins;

/**
 * The class reads information in all of the plugin manifests in a repository and compiles a repository plugins.xml file.
 * 
 * @author Joseph Isaac
 *
 */
public class RepositoryMetadata {

    public static final String PLUGINS_PACKAGE = "org.tolven.plugin.repository.bean";
    public static final String PLUGIN_PACKAGE = "org.tolven.plugin.registry.xml.bean";
    public static final String LIBRARY_PLUGS_DIR = "plugins";
    public static final String METADATA_XML = "plugins.xml";
    public static final String PLUGINS_XSD = "plugins.xsd";
    public static final String INFO_TIMESTAMP_FORMAT = "yyyyMMddHHmmss";
    public static final String MESSAGE_DIGEST_ALGORITHM = "md5";
    private static final String INTEGER_MAX_STRING = String.valueOf(Integer.MAX_VALUE);

    private File outDir;
    private ObjectFactory objectFactory;
    private Logger logger = Logger.getLogger(RepositoryMetadata.class);
    private static JAXBContext jaxbContext;

    public RepositoryMetadata() {
    }

    public File getXmlOutputDir() {
        return outDir;
    }

    public void setXmlOutputDir(File outDir) {
        this.outDir = outDir;
    }

    private ObjectFactory getObjectFactory() {
        return objectFactory;
    }

    private void setObjectFactory(ObjectFactory objectFactory) {
        this.objectFactory = objectFactory;
    }

    private static JAXBContext getJaxbContext() {
        if (jaxbContext == null) {
            try {
                jaxbContext = JAXBContext.newInstance(PLUGINS_PACKAGE, ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                throw new RuntimeException("Could not create JAXBContext", ex);
            }
        }
        return jaxbContext;
    }

    public void generate(String pluginsDirname, boolean merge, String libraryURLString, File outDir) {
        logger.debug("*** start ***");
        setXmlOutputDir(outDir);
        setObjectFactory(new ObjectFactory());
        File sourcePluginsDir = new File(pluginsDirname);
        if (!sourcePluginsDir.exists()) {
            throw new RuntimeException(sourcePluginsDir.getPath() + " does not exist");
        }
        URL sourceLibraryPluginsURL;
        try {
            sourceLibraryPluginsURL = sourcePluginsDir.toURI().toURL();
        } catch (MalformedURLException ex) {
            throw new RuntimeException(
                    "Could not convert repository directory " + sourcePluginsDir.getPath() + " to a URL: ", ex);
        }
        logger.info("Reading Library plugins from: " + sourceLibraryPluginsURL.toExternalForm());
        URL libraryPluginsURL;
        try {
            libraryPluginsURL = new URL(libraryURLString);
        } catch (MalformedURLException ex) {
            throw new RuntimeException("Could not convert plugin URL " + libraryURLString + " to a URL: ", ex);
        }
        logger.info("URIs will generated from: " + libraryPluginsURL.toExternalForm());
        List<String> plugins = getLibraryPluginsList(sourcePluginsDir);
        Plugins libraryPlugins = getPlugins(plugins, merge, sourceLibraryPluginsURL.toExternalForm(),
                libraryPluginsURL.toExternalForm());
        libraryPlugins.setInfo(getInfo());
        if (getXmlOutputDir() == null) {
            logger.info(getPluginsXML(libraryPlugins));
        } else {
            File libraryPluginsFile = new File(getXmlOutputDir(), METADATA_XML);
            logger.info("Write Library plugins to " + libraryPluginsFile);
            libraryPluginsFile.getParentFile().mkdirs();
            try {
                FileUtils.writeStringToFile(libraryPluginsFile, getPluginsXML(libraryPlugins));
            } catch (IOException ex) {
                throw new RuntimeException(
                        "Could not write library plugins.xml to: " + libraryPluginsFile.getPath(), ex);
            }
        }
        logger.debug("*** end ***");
    }

    private InfoDetail getInfo() {
        InfoDetail info = getObjectFactory().createInfoDetail();
        SimpleDateFormat dateFormat = new SimpleDateFormat(INFO_TIMESTAMP_FORMAT);
        String timestamp = dateFormat.format(new Date());
        info.setTimestamp(timestamp);
        return info;
    }

    public List<String> getLibraryPluginsList(File pluginsDir) {
        List<String> plugins = new ArrayList<String>();
        File[] zipFiles = pluginsDir.listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                try {
                    String urlString = "jar:" + file.toURI().toURL() + "!/tolven-plugin.xml";
                    URL url = null;
                    url = new URL(urlString);
                    return file.isFile() && url.openConnection().getContentLength() != -1;
                } catch (IOException e) {
                    throw new RuntimeException("Could not access manifest for: " + file.getPath(), e);
                }
            }
        });
        for (File zipFile : zipFiles) {
            plugins.add(zipFile.getName());
        }
        return plugins;
    }

    private Plugins getPlugins(List<String> pluginList, boolean merge, String sourcePluginsURLString,
            String libraryURLString) {
        Plugins plugins = null;
        if (merge) {
            InputStream in = null;
            try {
                in = new URL(libraryURLString + "/" + METADATA_XML).openStream();
                String pluginsXML = IOUtils.toString(in);
                plugins = getPlugins(pluginsXML);
            } catch (Exception ex) {
                throw new RuntimeException("Could not read the " + METADATA_XML + " from: " + libraryURLString, ex);
            } finally {

            }
        } else {
            plugins = getObjectFactory().createPlugins();
        }
        ManifestParser manifestParser = new ManifestParser(false);
        for (String pluginString : pluginList) {
            URL sourcePluginZipURL;
            String sourcePluginZipURLString = sourcePluginsURLString + pluginString;
            try {
                sourcePluginZipURL = new URL(sourcePluginZipURLString);
            } catch (MalformedURLException ex) {
                throw new RuntimeException(
                        "Could not convert plugin zip " + sourcePluginZipURLString + " to a URL: ", ex);
            }
            URL finalZipURL;
            String finalZipURLString = libraryURLString + "/" + LIBRARY_PLUGS_DIR + "/" + pluginString;
            try {
                finalZipURL = new URL(finalZipURLString);
            } catch (MalformedURLException ex) {
                throw new RuntimeException("Could not convert plugin zip " + finalZipURLString + " to a URL: ", ex);
            }
            URL pluginManifestURL;
            String pluginManifestURLString = "jar:" + sourcePluginsURLString + "/" + pluginString
                    + "!/tolven-plugin.xml";
            try {
                pluginManifestURL = new URL(pluginManifestURLString);
            } catch (MalformedURLException ex) {
                throw new RuntimeException(
                        "Could not convert plugin manifest " + pluginManifestURLString + " to a URL: ", ex);
            }
            try {
                if (pluginManifestURL.openConnection().getContentLength() == -1) {
                    /*
                     * There is no manifest, therefore this is not a tolven plugin
                     */
                    continue;
                }
            } catch (IOException ex) {
                throw new RuntimeException("Could not open a connection to " + pluginManifestURL.toExternalForm(),
                        ex);
            }
            ModelPluginManifest pluginManifest;
            try {
                pluginManifest = manifestParser.parseManifest(pluginManifestURL.toURI().toURL());
            } catch (Exception ex) {
                throw new RuntimeException("Could not parse plugin manifest: " + pluginManifestURL.toExternalForm(),
                        ex);
            }
            String pluginId = pluginManifest.getId();
            String pluginVersionString = null;
            if (pluginManifest.getVersion() == null) {
                pluginVersionString = "";
            } else {
                pluginVersionString = pluginManifest.getVersion().toString();
            }
            if (pluginVersionString.length() == 0) {
                throw new RuntimeException(sourcePluginZipURL.toExternalForm() + " must have a version ");
            }
            if (!pluginString.matches(pluginId + "-" + pluginVersionString + ".zip")) {
                throw new RuntimeException(sourcePluginZipURL.toExternalForm()
                        + " has manifest naming mismatch with external plugin name i.e: " + pluginString + " != "
                        + (pluginId + "-" + pluginVersionString + ".zip"));
            }
            processPluginManifest(pluginManifest, sourcePluginZipURL, finalZipURL, merge, plugins);
            URL pluginFragmentURL;
            String pluginFragmentURLString = "jar:" + sourcePluginsURLString + "/" + pluginString
                    + "!/tolven-plugin-fragment.xml";
            try {
                pluginFragmentURL = new URL(pluginFragmentURLString);
            } catch (MalformedURLException ex) {
                throw new RuntimeException(
                        "Could not convert plugin fragment manifest: " + pluginFragmentURLString + " to a URL", ex);
            }
            try {
                if (pluginFragmentURL.openConnection().getContentLength() != -1) {
                    /*
                     * tolven-plugin-fragment.xml is an optional manifest.
                     */
                    logger.debug("\tFound PluginFragment: " + pluginFragmentURL.toExternalForm());
                    ModelPluginFragment pluginFragment;
                    try {
                        pluginFragment = (ModelPluginFragment) manifestParser
                                .parseManifest(pluginFragmentURL.toURI().toURL());
                    } catch (Exception ex) {
                        throw new RuntimeException(
                                "Could not parse plugin fragment: " + pluginFragmentURL.toExternalForm(), ex);
                    }
                    if (pluginFragment.getPrerequisites().size() != 1) {
                        throw new RuntimeException("Plugin Fragment: " + pluginFragmentURL.toExternalForm()
                                + " must have only one <import> tag, which must refer to plugin: "
                                + pluginFragmentURL.toExternalForm());
                    }
                    ModelPrerequisite prerequisite = pluginFragment.getPrerequisites().get(0);
                    String prerequisiteVersion = null;
                    if (prerequisite.getPluginVersion() == null) {
                        prerequisiteVersion = "";
                    } else {
                        prerequisiteVersion = prerequisite.getPluginVersion().toString();
                    }
                    if (!pluginVersionString.equals(prerequisiteVersion)) {
                        throw new RuntimeException("The <imported> plugin version referred to by: "
                                + pluginFragmentURL.toExternalForm() + " does not match the plugin version of: "
                                + pluginManifestURL.toExternalForm());
                    }
                    processPluginFragment(prerequisite, pluginFragment, plugins);
                }
            } catch (IOException ex) {
                throw new RuntimeException("Could not open a connection to " + pluginFragmentURL.toExternalForm(),
                        ex);
            }
        }
        return plugins;
    }

    private void processPluginManifest(ModelPluginManifest pluginManifest, URL sourcePluginZipURL, URL finalZipURL,
            boolean merge, Plugins processedPlugins) {
        String pluginId = pluginManifest.getId();
        PluginDetail pluginDetail = getPluginDetail(pluginId, processedPlugins);
        if (pluginDetail == null) {
            pluginDetail = createPluginDetail(pluginId);
            processedPlugins.getPlugin().add(pluginDetail);
        }
        String version = pluginManifest.getVersion().toString();
        if (version == null || version.length() == 0) {
            throw new RuntimeException(
                    "Plugin: " + pluginId + " at " + sourcePluginZipURL.toExternalForm() + " must have a version ");
        }
        PluginVersionDetail pluginVersion = getPluginVersionDetail(version, pluginDetail);
        if (pluginVersion == null) {
            pluginVersion = createPluginVersionDetail(version);
            pluginVersion.setUri(finalZipURL.toExternalForm());
            MessageDigestDetail messageDigest = getMessageDigest(sourcePluginZipURL);
            pluginVersion.setMessageDigest(messageDigest);
            pluginDetail.getVersion().add(pluginVersion);
        } else {
            if (merge) {
                MessageDigestDetail messageDigest = getMessageDigest(sourcePluginZipURL);
                if (!pluginVersion.getMessageDigest().getValue().equals(messageDigest.getValue())) {
                    pluginVersion.setMessageDigest(messageDigest);
                    logger.debug("Merged manifest for: " + sourcePluginZipURL.toExternalForm());
                }
            }
        }
        for (ModelPrerequisite prerequisite : pluginManifest.getPrerequisites()) {
            String requiredPluginId = prerequisite.getPluginId();
            String requiredVersion = null;
            if (prerequisite.getPluginVersion() == null) {
                requiredVersion = "";
            } else {
                requiredVersion = prerequisite.getPluginVersion().toString();
            }
            String requiredMatch = null;
            if (prerequisite.getMatchingRule() == null) {
                requiredMatch = "";
            } else {
                requiredMatch = prerequisite.getMatchingRule().toCode();
            }
            processPrerequisite(pluginDetail, pluginVersion, requiredPluginId, requiredVersion, requiredMatch,
                    processedPlugins);
        }
    }

    private void processPluginFragment(ModelPrerequisite prerequisite, ModelPluginFragment pluginFragment,
            Plugins processedPlugins) {
        String prerequisitePluginId = prerequisite.getPluginId();
        PluginDetail pluginDetail = getPluginDetail(prerequisitePluginId, processedPlugins);
        if (pluginDetail == null) {
            pluginDetail = createPluginDetail(prerequisitePluginId);
            processedPlugins.getPlugin().add(pluginDetail);
        }
        String prerequisiteVersionString = prerequisite.getPluginVersion().toString();
        PluginVersionDetail prerequisiteVersion = getPluginVersionDetail(prerequisiteVersionString, pluginDetail);
        if (prerequisiteVersion == null) {
            prerequisiteVersion = createPluginVersionDetail(prerequisiteVersionString);
            pluginDetail.getVersion().add(prerequisiteVersion);
        }
        String requiredPluginId = pluginFragment.getPluginId();
        String requiredPluginVersion = null;
        if (pluginFragment.getPluginVersion() == null) {
            requiredPluginVersion = "";
        } else {
            requiredPluginVersion = pluginFragment.getPluginVersion().toString();
        }
        String requiredPluginMatch = null;
        if (pluginFragment.getMatchingRule() == null) {
            requiredPluginMatch = "";
        } else {
            requiredPluginMatch = pluginFragment.getMatchingRule().toCode();
        }
        processPrerequisite(pluginDetail, prerequisiteVersion, requiredPluginId, requiredPluginVersion,
                requiredPluginMatch, processedPlugins);
    }

    private void processPrerequisite(PluginDetail plugin, PluginVersionDetail pluginVersion,
            String requiredPluginId, String requiredVersion, String requiredMatch, Plugins plugins) {
        DependentPluginDetail dependent = getObjectFactory().createDependentPluginDetail();
        dependent = getObjectFactory().createDependentPluginDetail();
        dependent.setId(plugin.getId());
        dependent.setVersion(pluginVersion.getId());
        if (!requiredVersion.isEmpty()) {
            if (requiredVersion.isEmpty()) {
                dependent.setRequiresMinVersion(requiredVersion);
                if (requiredMatch.equals("equals")) {
                    dependent.setRequiresMaxVersion(requiredVersion);
                } else if (requiredMatch.equals("greater-or-equal")) {
                    dependent.setRequiresMaxVersion(null);
                } else {
                    throw new RuntimeException("Not a supported match value: " + requiredMatch);
                }
            }
        }
        PluginDetail requiredPlugin = getPluginDetail(requiredPluginId, plugins);
        if (requiredPlugin == null) {
            requiredPlugin = createPluginDetail(requiredPluginId);
            plugins.getPlugin().add(requiredPlugin);
        }
        if (getDependentPlugin(dependent, requiredPlugin) == null) {
            requiredPlugin.getDependent().add(dependent);
        }
    }

    private DependentPluginDetail getDependentPlugin(DependentPluginDetail dependentPlugin, PluginDetail plugin) {
        for (DependentPluginDetail dp : plugin.getDependent()) {
            if (dp.getId().equals(dependentPlugin.getId())
                    && dp.getVersion().equals(dependentPlugin.getVersion())) {
                if (dp.getRequiresMinVersion() == null && dependentPlugin.getRequiresMinVersion() != null) {
                    return null;
                }
                if (dp.getRequiresMinVersion() != null
                        && !dp.getRequiresMinVersion().equals(dependentPlugin.getRequiresMinVersion())) {
                    return null;
                }
                if (dp.getRequiresMaxVersion() == null && dependentPlugin.getRequiresMaxVersion() != null) {
                    return null;
                }
                if (dp.getRequiresMaxVersion() != null
                        && !dp.getRequiresMaxVersion().equals(dependentPlugin.getRequiresMaxVersion())) {
                    return null;
                }
                return dp;
            }
        }
        return null;
    }

    private MessageDigestDetail getMessageDigest(URL pluginURL) {
        MessageDigestDetail messageDetail = getObjectFactory().createMessageDigestDetail();
        String digest = TolvenMessageDigest.checksum(pluginURL, MESSAGE_DIGEST_ALGORITHM);
        messageDetail.setType(MESSAGE_DIGEST_ALGORITHM);
        messageDetail.setValue(digest);
        return messageDetail;
    }

    public static PluginDetail getPluginDetail(String pluginId, Plugins plugins) {
        for (PluginDetail pluginDetail : plugins.getPlugin()) {
            if (pluginDetail.getId().equals(pluginId)) {
                return pluginDetail;
            }
        }
        return null;
    }

    private PluginDetail createPluginDetail(String pluginId) {
        PluginDetail pluginDetail = getObjectFactory().createPluginDetail();
        pluginDetail.setId(pluginId);
        return pluginDetail;
    }

    public static PluginVersionDetail getPluginVersionDetail(String version, PluginDetail plugin) {
        for (PluginVersionDetail pluginDetailVersion : plugin.getVersion()) {
            if (pluginDetailVersion.getId().equals(version)) {
                return pluginDetailVersion;
            }
        }
        return null;
    }

    private PluginVersionDetail createPluginVersionDetail(String version) {
        PluginVersionDetail pluginDetailVersion = getObjectFactory().createPluginVersionDetail();
        pluginDetailVersion.setId(version);
        return pluginDetailVersion;
    }

    public static Plugins getRepositoryPlugins(URL repositoryURL) {
        try {
            Plugins plugins = getPlugins(new URL(repositoryURL.toExternalForm() + "/" + METADATA_XML));
            return plugins;
        } catch (MalformedURLException ex) {
            throw new RuntimeException(
                    "Problem appending " + "/" + METADATA_XML + " to URL " + repositoryURL.toExternalForm(), ex);
        }
    }

    public static Plugins getPlugins(URL pluginsXMLURL) {
        Logger.getLogger(RepositoryMetadata.class).info("Plugins URL: '" + pluginsXMLURL.toExternalForm() + "'");
        try {
            InputStream in = null;
            try {
                in = pluginsXMLURL.openStream();
                return getPlugins(in);
            } finally {
                if (in != null) {
                    in.close();
                }
            }
        } catch (IOException ex) {
            throw new RuntimeException("Could not load plugins from: " + pluginsXMLURL.toExternalForm(), ex);
        }
    }

    /**
     * Unmarshall a Plugins from a String
     * 
     * @param pluginsXML
     * @return
     */
    public static Plugins getPlugins(String pluginsXML) {
        try {
            StringReader reader = new StringReader(pluginsXML);
            Unmarshaller u = getJaxbContext().createUnmarshaller();
            Plugins plugins = (Plugins) u.unmarshal(reader);
            return plugins;
        } catch (JAXBException ex) {
            throw new RuntimeException("Could not read plugins from: " + pluginsXML, ex);
        }
    }

    public static Plugin getPlugin(InputStream xsdStream) {
        try {
            Unmarshaller u = getJaxbContext().createUnmarshaller();
            return (Plugin) u.unmarshal(xsdStream);
        } catch (JAXBException ex) {
            throw new RuntimeException("Could not load plugins inputstream", ex);
        }
    }

    public static Plugin getPlugin(URL pluginManifest) {
        InputStream in = null;
        try {
            try {
                in = pluginManifest.openStream();
                return getPlugin(in);
            } finally {
                if (in != null) {
                    in.close();
                }
            }
        } catch (IOException ex) {
            throw new RuntimeException("Could not read plugins from: " + pluginManifest.toExternalForm(), ex);
        }

    }

    public static PluginFragment getPluginFragment(InputStream xsdStream) {
        try {
            Unmarshaller u = getJaxbContext().createUnmarshaller();
            return (PluginFragment) u.unmarshal(xsdStream);
        } catch (JAXBException ex) {
            throw new RuntimeException("Could not load plugins inputstream", ex);
        }
    }

    public static PluginFragment getPluginFragment(URL pluginFragManifest) {
        InputStream in = null;
        try {
            try {
                in = pluginFragManifest.openStream();
                return getPluginFragment(in);
            } finally {
                if (in != null) {
                    in.close();
                }
            }
        } catch (IOException ex) {
            throw new RuntimeException("Could not read plugins from: " + pluginFragManifest.toExternalForm(), ex);
        }
    }

    public static String getPluginManifest(Plugin plugin) {
        try {
            StringWriter stringWriter = new StringWriter();
            Marshaller marshaller = getJaxbContext().createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION,
                    "urn:tolven-org:tpf:1.0 http://tolven.org/xsd/tpf.xsd");
            marshaller.marshal(plugin, stringWriter);
            return stringWriter.toString();
        } catch (JAXBException ex) {
            throw new RuntimeException("Could not convert plugin manifest to string for plugin: " + plugin.getId(),
                    ex);
        }
    }

    public static String getPluginFragmentManifest(PluginFragment pluginFragment) {
        try {
            StringWriter stringWriter = new StringWriter();
            Marshaller marshaller = getJaxbContext().createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            marshaller.marshal(pluginFragment, stringWriter);
            return stringWriter.toString();
        } catch (JAXBException ex) {
            throw new RuntimeException(
                    "Could not convert plugin fragment manifest to string for plugin: " + pluginFragment.getId(),
                    ex);
        }
    }

    /**
     * Unmarshall a Plugins from an InputStream
     * @param xmlStream
     * @return
     */
    public static Plugins getPlugins(InputStream xmlStream) {
        try {
            Unmarshaller u = getJaxbContext().createUnmarshaller();
            Plugins plugins = (Plugins) u.unmarshal(xmlStream);
            return plugins;
        } catch (JAXBException ex) {
            throw new RuntimeException("Could not load plugins inputstream", ex);
        }
    }

    public static String getPluginsXML(Plugins plugins) {
        try {
            StringWriter stringWriter = new StringWriter();
            Marshaller marshaller = getJaxbContext().createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            marshaller.marshal(plugins, stringWriter);
            return stringWriter.toString();
        } catch (JAXBException ex) {
            throw new RuntimeException("Could not convert Plugins to a string", ex);
        }
    }

    public static Plugins copyPlugins(Plugins plugins) {
        return getPlugins(getPluginsXML(plugins));
    }

    public static PluginVersionDetail getLatestVersion(final PluginDetail plugin) {
        return getLatestVersion(plugin, new ArrayList<ConstraintDetail>());
    }

    public static PluginVersionDetail getLatestVersion(final PluginDetail plugin,
            List<ConstraintDetail> constraints) {
        if (plugin.getVersion().isEmpty()) {
            NoVersionException ex = new NoVersionException("No versions exist for plugin: " + plugin.getId());
            ex.setPlugin(plugin);
            throw ex;
        }
        List<PluginVersionDetail> matchingVersions = getMatchingVersions(plugin, constraints);
        if (matchingVersions.isEmpty()) {
            NoVersionMatchException ex = new NoVersionMatchException(
                    "No matching versions exist for plugin: " + plugin.getId());
            ex.setPlugin(plugin);
            throw ex;
        }
        PluginVersionDetail latestPluginVersion = null;
        for (PluginVersionDetail pluginVersion : matchingVersions) {
            if (latestPluginVersion == null
                    || VersionNumber.compare(pluginVersion.getId(), latestPluginVersion.getId()) > 0) {
                latestPluginVersion = pluginVersion;
            }
        }
        return latestPluginVersion;
    }

    private static List<PluginVersionDetail> getMatchingVersions(PluginDetail plugin,
            List<ConstraintDetail> constraints) {
        List<PluginVersionDetail> matchingVersions = new ArrayList<PluginVersionDetail>();
        for (PluginVersionDetail pluginVersion : plugin.getVersion()) {
            boolean pluginIdMatched = false;
            for (ConstraintDetail constraint : constraints) {
                String constraintPluginId = constraint.getPluginId();
                if (plugin.getId().matches(constraintPluginId)) {
                    pluginIdMatched = true;
                    if ((constraint.getMinVersion() == null || constraint.getMinVersion().length() == 0
                            || VersionNumber.compare(pluginVersion.getId(), constraint.getMinVersion()) >= 0)
                            && (constraint.getMaxVersion() == null || constraint.getMaxVersion().length() == 0
                                    || VersionNumber.compare(pluginVersion.getId(),
                                            constraint.getMaxVersion().replace("*", INTEGER_MAX_STRING)) <= 0)) {
                        matchingVersions.add(pluginVersion);
                    }
                }
            }
            if (!pluginIdMatched) {
                matchingVersions.add(pluginVersion);
            }
        }
        return matchingVersions;
    }

    public static String getVersion(URL pluginManifestURL) {
        ModelPluginManifest pluginManifest = null;
        try {
            ManifestParser manifestParser = new ManifestParser(false);
            pluginManifest = manifestParser.parseManifest(pluginManifestURL.toURI().toURL());
        } catch (Exception ex) {
            throw new RuntimeException("Could not parse plugin manifest: " + pluginManifestURL.toExternalForm(),
                    ex);
        }
        if (pluginManifest.getVersion() == null) {
            throw new RuntimeException(pluginManifestURL.toExternalForm() + " has no version ");
        } else {
            return pluginManifest.getVersion().toString();
        }
    }

    public static String format(URL pluginsXMLURL) {
        Plugins plugins = getPlugins(pluginsXMLURL);
        return format(plugins);
    }

    public static String format(Plugins plugins) {
        List<PluginDetail> pluginDetails = plugins.getPlugin();
        Comparator<PluginDetail> pluginComparator = new Comparator<PluginDetail>() {
            public int compare(PluginDetail plugin1, PluginDetail plugin2) {
                return (plugin1.getId().compareTo(plugin2.getId()));
            };
        };
        Collections.sort(pluginDetails, pluginComparator);
        Comparator<PluginPropertyDetail> propertyDetailComparator = new Comparator<PluginPropertyDetail>() {
            public int compare(PluginPropertyDetail property1, PluginPropertyDetail property2) {
                return (property1.getName().compareTo(property2.getName()));
            };
        };
        for (PluginDetail pluginDetail : pluginDetails) {
            List<PluginPropertyDetail> propertyDetails = pluginDetail.getProperty();
            Collections.sort(propertyDetails, propertyDetailComparator);
        }
        List<PluginPropertyDetail> propertyDetails = plugins.getProperty();
        Collections.sort(propertyDetails, propertyDetailComparator);
        return getPluginsXML(plugins);
    }

    public static void main(String[] args) {
        Options cmdLineOptions = new Options();
        Option pluginsDirOption = new Option("plugins", "plugins", true, "plugins directory");
        pluginsDirOption.setRequired(true);
        cmdLineOptions.addOption(pluginsDirOption);
        cmdLineOptions.addOption(new Option("merge", "merge", false, "Merge pluginsDir into metadata"));
        Option libraryURLOption = new Option("liburl", "liburl", true, "library plugins URL");
        libraryURLOption.setRequired(true);
        cmdLineOptions.addOption(libraryURLOption);
        Option outdirDirOption = new Option("outdir", "outdir", true, "metadata output directory");
        outdirDirOption.setRequired(true);
        cmdLineOptions.addOption(outdirDirOption);
        GnuParser parser = new GnuParser();
        CommandLine commandLine = null;
        try {
            commandLine = parser.parse(cmdLineOptions, args);
        } catch (ParseException ex) {
            HelpFormatter formatter = new HelpFormatter();
            formatter.printHelp(org.tolven.plugin.repository.RepositoryMetadata.class.getName(), cmdLineOptions);
            throw new RuntimeException("Could not parse command line for: "
                    + org.tolven.plugin.repository.RepositoryMetadata.class.getName(), ex);
        }
        String pluginsDirname = commandLine.getOptionValue("plugins");
        boolean merge = commandLine.hasOption("merge");
        String libraryURLString = commandLine.getOptionValue("liburl");
        String outDirname = commandLine.getOptionValue("outdir");
        File outDir = new File(outDirname);
        new RepositoryMetadata().generate(pluginsDirname, merge, libraryURLString, outDir);
    }

}