org.fabric3.contribution.DependencyResolverImpl Maven / Gradle / Ivy
The newest version!
/*
* Fabric3
* Copyright (c) 2009-2015 Metaform Systems
*
* Licensed 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.
* Portions originally based on Apache Tuscany 2007
* licensed under the Apache 2.0 license.
*/
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 java.util.stream.Collectors;
import org.fabric3.api.host.Fabric3Exception;
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;
import org.oasisopen.sca.annotation.Reference;
/**
* Orders contribution dependencies by resolving imports and capabilities and then performing a topological sort of the dependency graph.
*/
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) {
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("Cycles detected");
}
try {
List> vertices = sorter.sort(dag);
List ordered = new ArrayList<>(vertices.size());
ordered.addAll(vertices.stream().map(Vertex::getEntity).collect(Collectors.toList()));
return ordered;
} catch (GraphException e) {
// this is a programming error
throw new AssertionError(e);
}
}
public List orderContributionAndDependents(URI uri) {
Contribution contribution = store.find(uri);
List dependents = new ArrayList<>();
Set contributions = store.getContributions();
addDependents(contribution, dependents, contributions);
return orderForUninstall(dependents);
}
private void addDependents(Contribution current, List dependents, Set contributions) {
dependents.add(current);
URI currentUri = current.getUri();
contributions.forEach(c -> {
if (dependents.contains(c)) {
return;
}
c.getWires().stream().filter(wire -> {
URI exportedUri = wire.getExportContributionUri();
return currentUri.equals(exportedUri);
}).forEach(wire -> addDependents(c, dependents, contributions));
});
}
/**
* 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
*/
private void resolveImports(Vertex source, DirectedGraph dag) {
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
*/
private void resolveExternalImport(Import imprt, Vertex source, DirectedGraph dag) {
// 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 Fabric3Exception("Unable to resolve import " + imprt + " in " + uri);
}
} 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
*/
private void resolveOverlappingImport(Import imprt, Iterator iterator, Vertex source, DirectedGraph dag) {
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) {
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() && ContributionState.DEPLOYED != resolved.getState()) {
throw new Fabric3Exception("Contribution " + contribution.getUri() + " requires a capability provided by " + resolved.getUri()
+ " which is not installed");
}
}
if (resolvedContributions.isEmpty()) {
throw new Fabric3Exception("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<>();
if (!imprt.getResolved().isEmpty()) {
// already resolved
for (Map.Entry entry : imprt.getResolved().entrySet()) {
for (Vertex vertex : dag.getVertices()) {
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
*/
private List sort(DirectedGraph dag) {
// detect cycles
List> cycles = detector.findCycles(dag);
if (!cycles.isEmpty()) {
// cycles were detected
StringBuilder builder = new StringBuilder();
for (Cycle cycle : cycles) {
for (Vertex vertex : cycle.getOriginPath()) {
builder.append(vertex.getEntity().getUri()).append("\n");
}
}
throw new Fabric3Exception("Cyclic dependencies found:\n" + builder);
}
try {
List> vertices = sorter.reverseSort(dag);
List ordered = new ArrayList<>(vertices.size());
ordered.addAll(vertices.stream().map(Vertex::getEntity).collect(Collectors.toList()));
return ordered;
} catch (GraphException e) {
throw new Fabric3Exception(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) {
if (imprt.isMultiplicity()) {
return; // multiplicity imports do not drop exports
}
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 (imprt.isMultiplicity()) {
return; // multiplicity imports do not drop exports
}
if (export.match(imprt)) {
iterator.remove();
break;
}
}
}
private void checkInstalled(Contribution contribution, List resolvedContributions) {
for (Contribution resolved : resolvedContributions) {
if (resolved != null && ContributionState.INSTALLED != resolved.getState() && ContributionState.DEPLOYED != resolved.getState()) {
throw new Fabric3Exception("Contribution " + contribution.getUri() + " imports " + resolved.getUri() + " which is not installed");
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy