Package org.apache.ace.deployment.provider.repositorybased

Source Code of org.apache.ace.deployment.provider.repositorybased.RepositoryBasedProvider$URLDirectivePair

/*
* 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.ace.deployment.provider.repositorybased;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.namespace.QName;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPathVariableResolver;

import org.apache.ace.client.repository.helper.bundle.BundleHelper;
import org.apache.ace.client.repository.object.DeploymentArtifact;
import org.apache.ace.deployment.provider.ArtifactData;
import org.apache.ace.deployment.provider.DeploymentProvider;
import org.apache.ace.deployment.provider.impl.ArtifactDataImpl;
import org.apache.ace.range.RangeIterator;
import org.apache.ace.repository.Repository;
import org.apache.ace.repository.ext.BackupRepository;
import org.apache.ace.repository.ext.CachedRepository;
import org.apache.ace.repository.ext.impl.CachedRepositoryImpl;
import org.apache.ace.repository.ext.impl.FilebasedBackupRepository;
import org.apache.ace.repository.ext.impl.RemoteRepository;
import org.osgi.framework.Version;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.osgi.service.log.LogService;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

/**
* The RepositoryBasedProvider provides version information and bundle data by the DeploymentProvider interface. It uses a
* Repository to get its information from, which it parses using a SAX parser.
*/
public class RepositoryBasedProvider implements DeploymentProvider, ManagedService {
    private static final String URL = "url";
    private static final String NAME = "name";
    private static final String CUSTOMER = "customer";
    private volatile LogService m_log;

    /** This variable is volatile since it can be changed by the Updated() method. */
    private volatile CachedRepository m_cachedRepository;

    /**
     * This variable is volatile since it can be changed by the updated() method. Furthermore, it will be used to inject a
     * custom repository in the integration test.
     */
    private volatile Repository m_directRepository;

    public List<ArtifactData> getBundleData(String gatewayId, String version) throws IllegalArgumentException, IOException {
        return getBundleData(gatewayId, null, version);
    }

    public List<ArtifactData> getBundleData(String gatewayId, String versionFrom, String versionTo) throws IllegalArgumentException, IOException {
        try {
            if (versionFrom != null) {
                Version.parseVersion(versionFrom);
            }
            Version.parseVersion(versionTo);
        }
        catch (NumberFormatException nfe) {
            throw new IllegalArgumentException(nfe);
        }

        InputStream input = null;
        List<ArtifactData> dataVersionTo = null;
        List<ArtifactData> dataVersionFrom = null;

        List<URLDirectivePair>[] pairs = null;
        try {
            input = getRepositoryStream();
            if (versionFrom == null) {
                pairs = getURLDirectivePairs(input, gatewayId, new String[] { versionTo });
            }
            else {
                pairs = getURLDirectivePairs(input, gatewayId, new String[] { versionFrom, versionTo });
            }
        }
        catch (IOException ioe) {
            m_log.log(LogService.LOG_WARNING, "Problem parsing source version.", ioe);
            throw ioe;
        }
        finally {
            if (input != null) {
                try {
                    input.close();
                }
                catch (IOException e) {
                    m_log.log(LogService.LOG_DEBUG, "Error closing stream", e);
                }
            }
        }

        if ((pairs != null) && (pairs.length > 1)) {
            dataVersionFrom = getBundleDataByDocument(pairs[0]);
            dataVersionTo = getBundleDataByDocument(pairs[1]);
            Iterator<ArtifactData> it = dataVersionTo.iterator();
            while (it.hasNext()) {
                ArtifactDataImpl bundleDataVersionTo = (ArtifactDataImpl) it.next();
                // see if there was previously a version of this bundle, and update the 'changed' property accordingly.
                if (bundleDataVersionTo.isBundle()) {
                    ArtifactData bundleDataVersionFrom = getBundleData(bundleDataVersionTo.getSymbolicName(), dataVersionFrom);
                    bundleDataVersionTo.setChanged(!bundleDataVersionTo.equals(bundleDataVersionFrom));
                }
                else {
                    ArtifactData bundleDataVersionFrom = getBundleData(bundleDataVersionTo.getUrl(), dataVersionFrom);
                    bundleDataVersionTo.setChanged(bundleDataVersionFrom == null);
                }
            }
        }
        else {
            dataVersionTo = getBundleDataByDocument(pairs[0]);
        }

        return dataVersionTo != null ? dataVersionTo : new ArrayList<ArtifactData>();
    }

    @SuppressWarnings("unchecked")
    public List<String> getVersions(String gatewayId) throws IllegalArgumentException, IOException {
        List<String> stringVersionList = new ArrayList<String>();
        InputStream input = null;

        try {
            input = getRepositoryStream();
            List<Version> versionList = getAvailableVersions(input, gatewayId);
            if (versionList.isEmpty()) {
                m_log.log(LogService.LOG_DEBUG, "No versions found for gateway " + gatewayId);
            }
            else {
                // now sort the list of versions and convert all values to strings.
                Collections.sort(versionList);
                Iterator<Version> it = versionList.iterator();
                while (it.hasNext()) {
                    String version = (it.next()).toString();
                    stringVersionList.add(version);
                }
            }
        }
        catch (IllegalArgumentException iae) {
            // just move on.
        }
        catch (IOException ioe) {
            m_log.log(LogService.LOG_DEBUG, "Problem parsing DeploymentRepository", ioe);
            throw ioe;
        }
        finally {
            if (input != null) {
                try {
                    input.close();
                }
                catch (IOException e) {
                    m_log.log(LogService.LOG_DEBUG, "Error closing stream", e);
                }
            }
        }

        return stringVersionList;
    }

    /**
     * Helper method to get the bundledata given an inputstream to a repository xml file
     *
     * @param input An input stream to the XML data to be parsed.
     * @return A list of ArtifactData object representing this version.
     */
    private List<ArtifactData> getBundleDataByDocument(List<URLDirectivePair> urlDirectivePairs) throws IllegalArgumentException {
        List<ArtifactData> result = new ArrayList<ArtifactData>();

        // get the bundledata for each URL
        for (URLDirectivePair pair : urlDirectivePairs) {
            Map<String, String> directives = pair.getDirective();

            if (directives.get(DeploymentArtifact.DIRECTIVE_KEY_PROCESSORID) == null) {
                // this is a bundle.
                String symbolicName = directives.remove(BundleHelper.KEY_SYMBOLICNAME);
                String bundleVersion = directives.remove(BundleHelper.KEY_VERSION);
                if (symbolicName != null) {
                    // it is the right symbolic name
                    if (symbolicName.trim().equals("")) {
                        m_log.log(LogService.LOG_WARNING, "Invalid bundle:" + pair.toString() + " the symbolic name is empty.");
                    }
                    else {
                        result.add(new ArtifactDataImpl(pair.getUrl(), directives, symbolicName, bundleVersion, true));
                    }
                }
            }
            else {
                // it is an artifact.
                result.add(new ArtifactDataImpl(pair.getUrl(), directives, true));
            }

        }
        return result;
    }

    /**
     * Helper method check for the existence of artifact data in the collection for a bundle with the given url.
     *
     * @param url The url to be found.
     * @return The <code>ArtifactData</code> object that has this <code>url</code>, or <code>null</code> if none can be
     *         found.
     */
    private ArtifactData getBundleData(URL url, Collection<ArtifactData> data) {
        ArtifactData bundle = null;
        Iterator<ArtifactData> it = data.iterator();
        while (it.hasNext()) {
            bundle = it.next();
            if (bundle.getUrl().equals(url)) {
                return bundle;
            }
        }
        return null;
    }

    /**
     * Helper method check for the existence of artifact data in the collection for a bundle with the given symbolic name.
     *
     * @param symbolicName The symbolic name to be found.
     * @return The <code>ArtifactData</code> object that has this <code>symbolicName</code>, or <code>null</code> if none
     *         can be found.
     */
    private ArtifactData getBundleData(String symbolicName, Collection<ArtifactData> data) {
        ArtifactData bundle = null;
        Iterator<ArtifactData> it = data.iterator();
        while (it.hasNext()) {
            bundle = it.next();
            if ((bundle.getSymbolicName() != null) && bundle.getSymbolicName().equals(symbolicName)) {
                return bundle;
            }
        }
        return null;
    }

    /**
     * Returns the available deployment versions for a gateway
     *
     * @param input A dom document representation of the repository
     * @param gatewayId The gatwayId
     * @return A list of available versions
     */
    private List<Version> getAvailableVersions(InputStream input, String gatewayId) throws IllegalArgumentException {
        //result list
        List<Version> versionList = new ArrayList<Version>();
        XPathContext context = XPathContext.getInstance();

        try {
            NodeList versions = context.getVersions(gatewayId, input);
            if (versions != null) {
                for (int j = 0; j < versions.getLength(); j++) {
                    Node n = versions.item(j);
                    String versionValue = n.getTextContent();
                    try {
                        Version version = Version.parseVersion(versionValue);
                        // no exception, but is could still be an empty version
                        if (!version.equals(Version.emptyVersion)) {
                            versionList.add(version);
                        }
                    }
                    catch (NumberFormatException nfe) {
                        // Ignore this version
                        m_log.log(LogService.LOG_WARNING, "Deploymentversion ignored: ", nfe);
                    }
                }
            }

            return versionList;
        }
        catch (XPathExpressionException xee) {
            throw new IllegalArgumentException(xee);
        }
        finally {
            context.destroy();
        }
    }

    /*
     * This class is used to cache compiled xpath expression for the queries we need on a per thread basis. In order
     * to do this a thread local is used to cache an instance of this class per calling thread. The reference to this
     * instance is wrapped in a soft reference to make it possible to GC the instance in case memory is low.
     * <p>
     * Example Usage:
     * <pre>
     * XPathContext context = XPathContext.getInstance();
     *
     * try
     * {
     *     // to get all artifacts of a number of versions:
     *     context.init(gatewayId, versions, input);
     *
     *     for (int i = 0; i < versions.length; i++)
     *     {
     *         Node version = context.getVersion(i);
     *         // Do work with version
     *     }
     *     // to get all versions of a number of a gateway:
     *     NodeList versions = context.getVersions(gatewayId, input);
     *     // Do wortk with versions
     * }
     * finally
     * {
     *     context.destory();
     * }
     * </pre>
     */
    private static final class XPathContext implements XPathVariableResolver {
        private static final ThreadLocal<SoftReference<XPathContext>> m_cache = new ThreadLocal<SoftReference<XPathContext>>();

        private final XPath m_xPath = XPathFactory.newInstance().newXPath();

        private final XPathExpression m_attributesExpression;

        private final XPathExpression m_versionsExpression;

        private final Map<Integer, XPathExpression> m_expressions = new HashMap<Integer, XPathExpression>();

        private String m_gatewayId;

        private String[] m_versions;

        private Node m_node;

        private String m_version;

        private XPathContext() {
            m_xPath.setXPathVariableResolver(this);
            try {
                m_attributesExpression = m_xPath.compile("//deploymentversions/deploymentversion/attributes/child::gatewayID[text()=$id]/../child::version[text()=$version]/../../artifacts");
                m_versionsExpression = m_xPath.compile("//deploymentversions/deploymentversion/attributes/child::gatewayID[text()=$id]/parent::attributes/version/text()");
            }
            catch (XPathExpressionException e) {
                throw new RuntimeException(e);
            }
        }

        /**
         * @return A thread local instance from the cache.
         */
        public static XPathContext getInstance() {
            SoftReference<XPathContext> ref = m_cache.get();
            XPathContext instance = null;
            if (ref != null) {
                instance = ref.get();
            }

            if (instance == null) {
                ref = null;
                instance = new XPathContext();
            }

            if (ref == null) {
                m_cache.set(new SoftReference<XPathContext>(instance));
            }

            return instance;
        }

        /**
         * @param gatewayId the id of the gateway
         * @param input the stream to read repository from
         * @return  the versions in the repo for the given gatewayId or null if none
         * @throws javax.xml.xpath.XPathExpressionException in case something goes wrong
         */
        public NodeList getVersions(String gatewayId, InputStream input) throws XPathExpressionException {
            m_gatewayId = gatewayId;
            return (NodeList) m_versionsExpression.evaluate(new InputSource(input), XPathConstants.NODESET);
        }

        /**
         * @param gatewayId the id of the gateway
         * @param versions the versions to return
         * @param input the stream to read repository from
         * @return true if versions can be found, otherwise false
         * @throws javax.xml.xpath.XPathExpressionException if something goes wrong
         */
        @SuppressWarnings("boxing")
        public boolean init(String gatewayId, String[] versions, InputStream input) throws XPathExpressionException {
            XPathExpression expression = m_expressions.get(versions.length);
            if (expression == null) {
                StringBuilder versionStatement = new StringBuilder("//deploymentversions/deploymentversion/attributes/child::gatewayID[text()=$id]/following::version[text()=$0");
                for (int i = 1; i < versions.length; i++) {
                    versionStatement.append(" or ").append(".=$").append(i);
                }
                versionStatement.append("]/../../..");
                expression = m_xPath.compile(versionStatement.toString());
                m_expressions.put(versions.length, expression);
            }
            m_gatewayId = gatewayId;
            m_versions = versions;

            m_node = (Node) expression.evaluate(new InputSource(input), XPathConstants.NODE);
            return (m_node != null);
        }

        /**
         *  @param i the index into the versions form init
         *  @return the version at index i
         * @throws javax.xml.xpath.XPathExpressionException if something goes wrong
         */
        public Node getVersion(int i) throws XPathExpressionException {
            m_version = m_versions[i];
            return (Node) m_attributesExpression.evaluate(m_node, XPathConstants.NODE);
        }

        /**
         * reset this thread local instance
         */
        public void destroy() {
            m_node = null;
            m_version = null;
            m_gatewayId = null;
            m_versions = null;
        }

        /**
         * @param name id|version|<version-index>
         * @return id->gatewayId | version->version | version-index -> versions[version-index]
         */
        public Object resolveVariable(QName name) {
            String localPart = name.getLocalPart();
            if ("id".equals(localPart)) {
                return m_gatewayId;
            }
            else if ("version".equals(localPart)) {
                return m_version;
            }
            return m_versions[Integer.parseInt(localPart)];
        }
    }

    /**
     * Helper method to retrieve urls and directives for a gateway-version combination.
     *
     * @param input An input stream from which an XML representation of a deployment repository can be read.
     * @param gatewayId The gatewayId to be used
     * @param versions An array of versions.
     * @return An array of lists of URLDirectivePairs. For each version in <code>versions</code>, a separate list will be
     *         created; the index of a version in the <code>versions</code> array is equal to the index of its result in the
     *         result array.
     * @throws IllegalArgumentException if the gatewayId or versions cannot be found in the input stream, or if
     *         <code>input</code> does not contain an XML stream.
     */
    @SuppressWarnings("unchecked")
    private List<URLDirectivePair>[] getURLDirectivePairs(InputStream input, String gatewayId, String[] versions) throws IllegalArgumentException {

        XPathContext context = XPathContext.getInstance();
        List<URLDirectivePair>[] result = new List[versions.length]; //unfortunately, we cannot use a typed list array.

        try {
            if (!context.init(gatewayId, versions, input)) {
                m_log.log(LogService.LOG_WARNING, "Versions not found for Gateway: " + gatewayId);
                throw new IllegalArgumentException("Versions not found.");
            }
            for (int i = 0; i < versions.length; i++) {
                result[i] = new ArrayList<URLDirectivePair>();

                // find all artifacts for the version we're currently working on.
                Node artifactNode = null;
                try {
                    artifactNode = context.getVersion(i);
                }
                catch (XPathExpressionException e) {
                    m_log.log(LogService.LOG_WARNING, "Version " + versions[i] + " not found for Gateway: " + gatewayId);
                    continue;
                }
                NodeList artifacts = artifactNode.getChildNodes();
                // Read the artifacts
                for (int artifactNumber = 0; artifactNumber < artifacts.getLength(); artifactNumber++) {
                    Node artifact = artifacts.item(artifactNumber);

                    NodeList artifactElements = artifact.getChildNodes();

                    String url = null;
                    Map<String, String> directives = new HashMap<String, String>();

                    for (int elementNumber = 0; elementNumber < artifactElements.getLength(); elementNumber++) {
                        // find the attributes of this artifact we are interested in.
                        Node element = artifactElements.item(elementNumber);

                        if (element.getNodeName().equals("url")) {
                            url = element.getTextContent();
                        }
                        else if (element.getNodeName().equals("directives")) {
                            // we found the directives? put all of them into our map.
                            NodeList directivesElements = element.getChildNodes();
                            for (int nDirective = 0; nDirective < directivesElements.getLength(); nDirective++) {
                                Node directivesElement = directivesElements.item(nDirective);
                                if (!"#text".equals(directivesElement.getNodeName())) {
                                    directives.put(directivesElement.getNodeName(), directivesElement.getTextContent());
                                }
                            }
                        }
                    }

                    if (url != null) {
                        try {
                            result[i].add(new URLDirectivePair(new URL(url), directives));
                        }
                        catch (MalformedURLException mue) {
                            m_log.log(LogService.LOG_WARNING, "The BundleUrl is malformed: ", mue);
                        }
                    }
                }
            }

            return result;
        }
        catch (XPathExpressionException ex) {
            throw new IllegalArgumentException(ex);
        }
        finally {
            context.destroy();
        }
    }

    /**
     * Helper to get an input stream to the currently used deployment repository.
     *
     * @return An input stream to the repository document. Will return an empty stream if none can be found.
     * @throws java.io.IOException if there is a problem communicating with the local or remote repository.
     */
    private InputStream getRepositoryStream() throws IOException {
        // cache the repositories, since we do not want them to change while we're in this method.
        CachedRepository cachedRepository = m_cachedRepository;
        Repository repository = m_directRepository;
        InputStream result;

        if (cachedRepository != null) {
            // we can use the cached repository
            if (cachedRepository.isCurrent()) {
                result = cachedRepository.getLocal(true);
            }
            else {
                result = cachedRepository.checkout(true);
            }
        }
        else {
            RangeIterator ri = repository.getRange().iterator();
            long resultVersion = 0;
            while (ri.hasNext()) {
                resultVersion = ri.next();
            }
            if (resultVersion != 0) {
                result = repository.checkout(resultVersion);
            }
            else {
                throw new IllegalArgumentException("There is no deployment information available.");
            }
        }

        return result;
    }

    @SuppressWarnings("unchecked")
    public void updated(Dictionary settings) throws ConfigurationException {
        if (settings != null) {
            String url = getNotNull(settings, URL, "DeploymentRepository URL not configured.");
            String name = getNotNull(settings, NAME, "RepositoryName not configured.");
            String customer = getNotNull(settings, CUSTOMER, "RepositoryCustomer not configured.");

            //create the remote repository and set it.
            try {
                BackupRepository backup = null;
                try {
                    backup = new FilebasedBackupRepository(File.createTempFile("currentrepository", null), File.createTempFile("backuprepository", null));
                }
                catch (Exception e) {
                    m_log.log(LogService.LOG_WARNING, "Unable to create temporary files for FilebasedBackupRepository");
                }

                // We always create the remote repository. If we can create a backup repository, we will wrap a CachedRepository
                // around it.
                m_directRepository = new RemoteRepository(new URL(url), customer, name);
                if (backup == null) {
                    m_cachedRepository = null;
                }
                else {
                    m_cachedRepository = new CachedRepositoryImpl(null, m_directRepository, backup, CachedRepositoryImpl.UNCOMMITTED_VERSION);
                }
            }
            catch (MalformedURLException mue) {
                throw new ConfigurationException(URL, mue.getMessage());
            }
        }
    }

    /**
     * Convenience method for getting settings from a configuration dictionary.
     */
    @SuppressWarnings("unchecked")
    private String getNotNull(Dictionary settings, String id, String errorMessage) throws ConfigurationException {
        String result = (String) settings.get(id);
        if (result == null) {
            throw new ConfigurationException(id, errorMessage);
        }
        return result;
    }

    /**
     * Helper class to store a pair of URL and directive, in which the directive may be empty.
     */
    private class URLDirectivePair {
        final private URL m_url;

        final private Map<String, String> m_directives;

        URLDirectivePair(URL url, Map<String, String> directives) {
            m_url = url;
            m_directives = directives;
        }

        public URL getUrl() {
            return m_url;
        }

        public Map<String, String> getDirective() {
            return m_directives;
        }
    }
}
TOP

Related Classes of org.apache.ace.deployment.provider.repositorybased.RepositoryBasedProvider$URLDirectivePair

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.