All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fabric3.contribution.DependencyResolverImpl Maven / Gradle / Ivy

/*
* Fabric3
* Copyright (c) 2009-2011 Metaform Systems
*
* Fabric3 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version, with the
* following exception:
*
* Linking this software statically or dynamically with other
* modules is making a combined work based on this software.
* Thus, the terms and conditions of the GNU General Public
* License cover the whole combination.
*
* As a special exception, the copyright holders of this software
* give you permission to link this software with independent
* modules to produce an executable, regardless of the license
* terms of these independent modules, and to copy and distribute
* the resulting executable under terms of your choice, provided
* that you also meet, for each linked independent module, the
* terms and conditions of the license of that module. An
* independent module is a module which is not derived from or
* based on this software. If you modify this software, you may
* extend this exception to your version of the software, but
* you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version.
*
* Fabric3 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 General Public License for more details.
*
* You should have received a copy of the
* GNU General Public License along with Fabric3.
* If not, see .
*/
package org.fabric3.contribution;

import java.net.URI;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.osoa.sca.annotations.Reference;

import org.fabric3.spi.contribution.Capability;
import org.fabric3.spi.contribution.Contribution;
import org.fabric3.spi.contribution.ContributionManifest;
import org.fabric3.spi.contribution.ContributionState;
import org.fabric3.spi.contribution.ContributionWire;
import org.fabric3.spi.contribution.Export;
import org.fabric3.spi.contribution.Import;
import org.fabric3.spi.contribution.MetaDataStore;
import org.fabric3.util.graph.Cycle;
import org.fabric3.util.graph.CycleDetector;
import org.fabric3.util.graph.CycleDetectorImpl;
import org.fabric3.util.graph.DirectedGraph;
import org.fabric3.util.graph.DirectedGraphImpl;
import org.fabric3.util.graph.Edge;
import org.fabric3.util.graph.EdgeImpl;
import org.fabric3.util.graph.GraphException;
import org.fabric3.util.graph.TopologicalSorter;
import org.fabric3.util.graph.TopologicalSorterImpl;
import org.fabric3.util.graph.Vertex;
import org.fabric3.util.graph.VertexImpl;

/**
 * Orders contribution dependencies by resolving imports and capabilities and then performing a topological sort of the dependency graph.
 *
 * @version $Rev: 10257 $ $Date: 2011-04-30 22:53:14 +0000 (Sat, 30 Apr 2011) $
 */
public class DependencyResolverImpl implements DependencyResolver {
    private CycleDetector detector;
    private TopologicalSorter sorter;
    private MetaDataStore store;


    public DependencyResolverImpl(@Reference MetaDataStore store) {
        this.store = store;
        detector = new CycleDetectorImpl();
        sorter = new TopologicalSorterImpl();
    }

    public List resolve(List contributions) throws DependencyException {
        DirectedGraph dag = new DirectedGraphImpl();
        // add the contributions as vertices
        for (Contribution contribution : contributions) {
            dag.add(new VertexImpl(contribution));
        }

        // add edges based on imports and capabilities
        for (Vertex source : dag.getVertices()) {
            resolveImports(source, dag);
            resolveCapabilities(source, dag);
        }

        // detect cycles
        return sort(dag);
    }

    public List orderForUninstall(List contributions) {
        // create a DAG
        DirectedGraph dag = new DirectedGraphImpl();
        // add the contributions as vertices
        for (Contribution contribution : contributions) {
            dag.add(new VertexImpl(contribution));
        }
        // add edges based on imports
        for (Vertex source : dag.getVertices()) {
            Contribution contribution = source.getEntity();
            URI uri = contribution.getUri();
            for (ContributionWire wire : contribution.getWires()) {
                for (Contribution entry : contributions) {
                    if (entry.getUri().equals(wire.getExportContributionUri())) {
                        Import imprt = wire.getImport();
                        List> sinks = resolveImport(imprt, uri, dag);
                        if (sinks.isEmpty()) {
                            // this should not happen
                            throw new AssertionError("Unable to resolve import " + imprt + " in " + uri);
                        }
                        for (Vertex sink : sinks) {
                            Edge edge = new EdgeImpl(source, sink);
                            dag.add(edge);
                        }
                        break;
                    }
                }
            }
        }
        // detect cycles
        List> cycles = detector.findCycles(dag);
        if (!cycles.isEmpty()) {
            // this is a programming error
            throw new AssertionError("Cylces detected");
        }
        try {
            List> vertices = sorter.sort(dag);
            List ordered = new ArrayList(vertices.size());
            for (Vertex vertex : vertices) {
                ordered.add(vertex.getEntity());
            }
            return ordered;
        } catch (GraphException e) {
            // this is a programming error
            throw new AssertionError(e);
        }
    }

    /**
     * Resolves imports for the contribution represented by the current DAG vertex. Resolution will be performed against contributions loaded
     * previously in the MetaDataStore and against contributions being loaded from the DAG. When an import is resolved by an export from
     * a contribution in the DAG, the later will be updated with an edge from the source contribution vertex to the target contribution vertex.
     *
     * @param source the contribution to resolve imports for
     * @param dag    the current contribution dag
     * @throws DependencyException if a resolution error occurs
     */
    private void resolveImports(Vertex source, DirectedGraph dag) throws DependencyException {
        Contribution contribution = source.getEntity();
        ContributionManifest manifest = contribution.getManifest();
        for (Iterator iterator = manifest.getImports().iterator(); iterator.hasNext();) {
            Import imprt = iterator.next();
            boolean hasExport = hasMatchingExport(contribution, imprt);
            if (hasExport) {
                resolveOverlappingImport(imprt, iterator, source, dag);
            } else {
                resolveExternalImport(imprt, source, dag);

            }
        }
    }

    /**
     * Resolves an import against other contributions loaded previously in the MetaDataStore and against contributions being loaded from
     * the DAG. When an import is resolved by an export from a contribution in the DAG, the later will be updated with an edge from the source
     * contribution vertex to the target contribution vertex.
     *
     * @param imprt  the import to resolve
     * @param source the contribution to resolve imports for
     * @param dag    the current contribution dag
     * @throws DependencyException if a resolution error occurs
     */
    private void resolveExternalImport(Import imprt, Vertex source, DirectedGraph dag) throws DependencyException {
        // See if the import is already stored. Extension imports do not need to be checked since we assume extensions are installed prior
        Contribution contribution = source.getEntity();
        URI uri = contribution.getUri();
        List> sinks = resolveImport(imprt, uri, dag);
        if (sinks.isEmpty()) {
            List resolvedContributions = store.resolve(uri, imprt);
            checkInstalled(contribution, resolvedContributions);
            if (resolvedContributions.isEmpty() && imprt.isRequired()) {
                throw new UnresolvableImportException("Unable to resolve import " + imprt + " in " + uri, imprt);
            }
        } else {
            for (Vertex sink : sinks) {
                Edge edge = new EdgeImpl(source, sink);
                dag.add(edge);
            }
        }
    }

    /**
     * Resolves an import where the contribution also exports the same symbol as the import (e.g. a Java package or qualified name).
     * 

* The following OSGi resolution algorithm defined in R4 Section 3.1 is followed: *

*

* External ? If the import resolves to an export statement in another bundle, then the overlapping export definition in this * contribution is discarded. *

*

* Internal ? If the import is resolved to an export statement in this module, then the overlapping import definition in this * contribution is discarded. *

*

* When an import is resolved by an export from a contribution in the DAG, the later will be updated with an edge from the source contribution * vertex to the target contribution vertex. * * @param imprt the import to resolve * @param iterator the import iterator - used to remove the import from the containing manifest if it is resolved by the export in the same * contribution * @param source the source contribution * @param dag the current DAG to resolve against * @throws DependencyException if there is a resolution error */ private void resolveOverlappingImport(Import imprt, Iterator iterator, Vertex source, DirectedGraph dag) throws DependencyException { Contribution contribution = source.getEntity(); ContributionManifest manifest = contribution.getManifest(); URI uri = contribution.getUri(); List> sinks = resolveImport(imprt, uri, dag); if (sinks.isEmpty()) { List resolvedContributions = store.resolve(uri, imprt); checkInstalled(contribution, resolvedContributions); if (resolvedContributions.isEmpty()) { // no external exports are found, i.e. internally resolved, drop the import iterator.remove(); } else { // externally resolved, drop the export dropExport(imprt, manifest); } } else { for (Vertex sink : sinks) { Edge edge = new EdgeImpl(source, sink); dag.add(edge); } // externally resolved, drop the export dropExport(imprt, manifest); } } private void resolveCapabilities(Vertex source, DirectedGraph dag) throws DependencyException { Contribution contribution = source.getEntity(); URI uri = contribution.getUri(); for (Capability capability : contribution.getManifest().getRequiredCapabilities()) { // See if a previously installed contribution supplies the capability List> sinks = findCapabilityVertices(capability, uri, dag); if (sinks.isEmpty()) { Set resolvedContributions = store.resolveCapability(capability.getName()); for (Contribution resolved : resolvedContributions) { if (resolved != null && ContributionState.INSTALLED != resolved.getState()) { throw new DependencyException("Contribution " + contribution.getUri() + " requires a capability provided by " + resolved.getUri() + " which is not installed"); } } if (resolvedContributions.isEmpty()) { throw new UnresolvableCapabilityException("Unable to resolve capability " + capability + " required by " + uri); } } else { for (Vertex sink : sinks) { Edge edge = new EdgeImpl(source, sink); dag.add(edge); } } } } /** * Resolve the import against the graph of contributions being loaded, returning the vertices in the graph with a matching export. Per OSGi, all * exports must be scanned to ensure that if a resolved export exists, it is used instead of an unresolved on. * * @param imprt the import to resolve * @param contributionUri the importing contribution URI * @param dag the graph to resolve against * @return the matching Vertex or null */ private List> resolveImport(Import imprt, URI contributionUri, DirectedGraph dag) { List> vertices = new ArrayList>(); Map, Export> candidates = new LinkedHashMap, Export>(); if (!imprt.getResolved().isEmpty()) { // already resolved for (Map.Entry entry : imprt.getResolved().entrySet()) { for (Vertex vertex : vertices) { if (vertex.getEntity().getUri().equals(entry.getKey())) { vertices.add(vertex); break; } } } return vertices; } for (Vertex vertex : dag.getVertices()) { Contribution contribution = vertex.getEntity(); ContributionManifest manifest = contribution.getManifest(); URI location = imprt.getLocation(); for (Export export : manifest.getExports()) { URI exportUri = contribution.getUri(); // also compare the contribution URI to avoid resolving to a contribution that imports and exports the same namespace if (export.match(imprt) && !contributionUri.equals(contribution.getUri())) { if (location != null) { // explicit location specified if (location.equals(exportUri)) { vertices.add(vertex); imprt.addResolved(exportUri, export); export.resolve(); dropImport(export, manifest); return vertices; // done since explicit locations must resolve to one contribution } } else { if (!imprt.isMultiplicity() && export.isResolved()) { // single value to resolve - return the vertex since the export matches and is already resolved for an import vertices.add(vertex); imprt.addResolved(exportUri, export); return vertices; } // export matches, track it in the candidates candidates.put(vertex, export); // multiplicity, check other vertices break; } } } } // first pass over candidates - select previously resolved exports for (Map.Entry, Export> entry : candidates.entrySet()) { Vertex vertex = entry.getKey(); Export export = entry.getValue(); if (export.isResolved()) { vertices.add(vertex); imprt.addResolved(vertex.getEntity().getUri(), export); if (!imprt.isMultiplicity()) { // not a multiplicity, return the resolved export return vertices; } } } // there were no resolved exports. loop through and select all exports for a multiplicity or the first export if (vertices.isEmpty() && !candidates.isEmpty()) { for (Map.Entry, Export> entry : candidates.entrySet()) { Vertex vertex = entry.getKey(); Export export = entry.getValue(); vertices.add(vertex); imprt.addResolved(vertex.getEntity().getUri(), export); export.resolve(); dropImport(export, vertex.getEntity().getManifest()); if (!imprt.isMultiplicity()) { return vertices; } } } return vertices; } /** * Returns true if the import has a matching export on the contribution. * * @param contribution the contribution * @param imprt the import * @return true if the import has a matching export on the contribution */ private boolean hasMatchingExport(Contribution contribution, Import imprt) { for (Export export : contribution.getManifest().getExports()) { if (export.match(imprt)) { return true; } } return false; } /** * Sorts the DAG topologically, ordering dependencies first. * * @param dag the DAG * @return the sorted contributions * @throws DependencyException if there is an error sorting the DAG */ private List sort(DirectedGraph dag) throws DependencyException { // detect cycles List> cycles = detector.findCycles(dag); if (!cycles.isEmpty()) { // cycles were detected throw new CyclicDependencyException(cycles); } try { List> vertices = sorter.reverseSort(dag); List ordered = new ArrayList(vertices.size()); for (Vertex vertex : vertices) { ordered.add(vertex.getEntity()); } return ordered; } catch (GraphException e) { throw new DependencyException(e); } } /** * Finds vertices in the graph providing the given capability. * * @param capability the capability * @param contributionUri the current contribution URI; used to avoid creating a cycle * @param dag the graph * @return the vertices */ private List> findCapabilityVertices(Capability capability, URI contributionUri, DirectedGraph dag) { List> vertices = new ArrayList>(); for (Vertex vertex : dag.getVertices()) { Contribution contribution = vertex.getEntity(); if (contribution.getManifest().getProvidedCapabilities().contains(capability) && !contributionUri.equals(contribution.getUri())) { vertices.add(vertex); break; } } return vertices; } private void dropExport(Import imprt, ContributionManifest manifest) { for (Iterator iterator = manifest.getExports().iterator(); iterator.hasNext();) { Export export = iterator.next(); if (export.match(imprt)) { iterator.remove(); break; } } } private void dropImport(Export export, ContributionManifest manifest) { for (Iterator iterator = manifest.getImports().iterator(); iterator.hasNext();) { Import imprt = iterator.next(); if (export.match(imprt)) { iterator.remove(); break; } } } private void checkInstalled(Contribution contribution, List resolvedContributions) throws DependencyException { for (Contribution resolved : resolvedContributions) { if (resolved != null && ContributionState.INSTALLED != resolved.getState()) { throw new DependencyException("Contribution " + contribution.getUri() + " imports " + resolved.getUri() + " which is not installed"); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy