From e35c666f9ab44cf37b39d83fb063e4c4f9c5eccb Mon Sep 17 00:00:00 2001 From: Tom Watson Date: Fri, 6 Jan 2017 17:20:15 +0000 Subject: [PATCH] FELIX-5482 - Contribute Resolver 1.1 implementation for OSGi R7 git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1777659 13f79535-47bb-0310-9956-ffa450edef68 --- osgi-r7/resolver/pom.xml | 7 +- .../org/apache/felix/resolver/Candidates.java | 16 +- .../felix/resolver/FelixResolveContext.java | 44 ----- .../apache/felix/resolver/ResolverImpl.java | 171 ++++++++++-------- .../service/resolver/HostedCapability.java | 8 +- .../service/resolver/ResolutionException.java | 19 +- .../osgi/service/resolver/ResolveContext.java | 93 ++++++++-- .../org/osgi/service/resolver/Resolver.java | 68 ++++++- .../osgi/service/resolver/package-info.java | 15 +- .../org/osgi/service/resolver/packageinfo | 1 - .../felix/resolver/test/ResolverTest.java | 8 +- .../resolver/test/util/IterativeResolver.java | 7 + 12 files changed, 290 insertions(+), 167 deletions(-) delete mode 100644 osgi-r7/resolver/src/main/java/org/apache/felix/resolver/FelixResolveContext.java delete mode 100644 osgi-r7/resolver/src/main/java/org/osgi/service/resolver/packageinfo diff --git a/osgi-r7/resolver/pom.xml b/osgi-r7/resolver/pom.xml index a336396b4d9..d102c4ed150 100644 --- a/osgi-r7/resolver/pom.xml +++ b/osgi-r7/resolver/pom.xml @@ -20,7 +20,7 @@ org.apache.felix felix-parent - 3 + 4 ../pom/pom.xml 4.0.0 @@ -37,6 +37,11 @@ http://svn.apache.org/repos/asf/felix/resolver + + org.osgi + osgi.annotation + 6.0.1 + org.osgi org.osgi.core diff --git a/osgi-r7/resolver/src/main/java/org/apache/felix/resolver/Candidates.java b/osgi-r7/resolver/src/main/java/org/apache/felix/resolver/Candidates.java index 29b2b7b10af..0d3f91850bc 100644 --- a/osgi-r7/resolver/src/main/java/org/apache/felix/resolver/Candidates.java +++ b/osgi-r7/resolver/src/main/java/org/apache/felix/resolver/Candidates.java @@ -184,17 +184,15 @@ public void populate(Collection resources) addCandidates(result.candidates); result.candidates = null; result.remaining = null; - if ((rc instanceof FelixResolveContext) && !Util.isFragment(resource)) + Collection relatedResources = rc.findRelatedResources(resource); + m_session.setRelatedResources(resource, relatedResources); + for (Resource relatedResource : relatedResources) { - Collection ondemandFragments = ((FelixResolveContext) rc).getOndemandResources(resource); - for (Resource fragment : ondemandFragments) + if (m_session.isValidRelatedResource(relatedResource)) { - if (m_session.isValidOnDemandResource(fragment)) - { - // This resource is a valid on demand resource; - // populate it now, consider it optional - toPopulate.addFirst(fragment); - } + // This resource is a valid related resource; + // populate it now, consider it optional + toPopulate.addFirst(relatedResource); } } continue; diff --git a/osgi-r7/resolver/src/main/java/org/apache/felix/resolver/FelixResolveContext.java b/osgi-r7/resolver/src/main/java/org/apache/felix/resolver/FelixResolveContext.java deleted file mode 100644 index 502a7e4593e..00000000000 --- a/osgi-r7/resolver/src/main/java/org/apache/felix/resolver/FelixResolveContext.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.felix.resolver; - -import java.util.Collection; -import org.osgi.resource.Resource; - -public interface FelixResolveContext -{ - /** - * Return the resources that the resolver should attempt to resolve on - * demand for specified resource which is being resolved. Inability to - * resolve one of the on demand resources will not result in a resolution - * exception. - * - *

- * The resolver will ask for on demand resources for each resource that is - * getting pulled into a resolve operation. An example of an on demand - * resource is a fragment. When a host is being resolved the resolve context - * will be asked if any additional resources should be added to the resolve - * operation. The resolve context may decide that the potential fragments of - * the host should be resolved along with the host. - * - * @return A collection of the resources that the resolver should attempt to - * resolve for this resolve context. May be empty if there are no on demand - * resources. The returned collection may be unmodifiable. - */ - public Collection getOndemandResources(Resource host); -} diff --git a/osgi-r7/resolver/src/main/java/org/apache/felix/resolver/ResolverImpl.java b/osgi-r7/resolver/src/main/java/org/apache/felix/resolver/ResolverImpl.java index 13c7ca4f722..b7e027223ef 100644 --- a/osgi-r7/resolver/src/main/java/org/apache/felix/resolver/ResolverImpl.java +++ b/osgi-r7/resolver/src/main/java/org/apache/felix/resolver/ResolverImpl.java @@ -51,7 +51,7 @@ enum PermutationType { // Note this class is not thread safe. // Only use in the context of a single thread. - static class ResolveSession + static class ResolveSession implements Runnable { // Holds the resolve context for this session private final ResolveContext m_resolveContext; @@ -60,9 +60,11 @@ static class ResolveSession private final Resource m_dynamicHost; private final Requirement m_dynamicReq; private final List m_dynamicCandidates; - // keeps track of valid on demand fragments that we have seen. + // keeps track of valid related resources that we have seen. // a null value or TRUE indicate it is valid - Map m_validOnDemandResources = new HashMap(0); + private Map m_validRelatedResources = new HashMap(0); + // keeps track of related resources for each resource + private Map> m_relatedResources = new HashMap>(0); // Holds candidate permutations based on permutating "uses" chains. // These permutations are given higher priority. private final List m_usesPermutations = new LinkedList(); @@ -86,8 +88,19 @@ static class ResolveSession private final Set m_sub_mutated = new HashSet(); private final ConcurrentMap> m_usesCache = new ConcurrentHashMap>(); private ResolutionError m_currentError; + volatile private CancellationException m_isCancelled = null; - ResolveSession(ResolveContext resolveContext, Executor executor, Resource dynamicHost, Requirement dynamicReq, List dynamicCandidates) + static ResolveSession createSession(ResolveContext resolveContext, Executor executor, Resource dynamicHost, Requirement dynamicReq, List dynamicCandidates) + { + ResolveSession session = new ResolveSession(resolveContext, executor, dynamicHost, dynamicReq, dynamicCandidates); + // call onCancel first + session.getContext().onCancel(session); + // now gather the mandatory and optional resources + session.initMandatoryAndOptionalResources(); + return session; + } + + private ResolveSession(ResolveContext resolveContext, Executor executor, Resource dynamicHost, Requirement dynamicReq, List dynamicCandidates) { m_resolveContext = resolveContext; m_executor = executor; @@ -98,12 +111,18 @@ static class ResolveSession m_mandatoryResources = Collections.singletonList(dynamicHost); m_optionalResources = Collections.emptyList(); } else { - // Make copies of arguments in case we want to modify them. - m_mandatoryResources = new ArrayList(resolveContext.getMandatoryResources()); - m_optionalResources = new ArrayList(resolveContext.getOptionalResources()); + // Do not call resolve context yet, onCancel must be called first + m_mandatoryResources = new ArrayList(); + m_optionalResources = new ArrayList(); } } + private void initMandatoryAndOptionalResources() { + if (!isDynamic()) { + m_mandatoryResources.addAll(getContext().getMandatoryResources()); + m_optionalResources.addAll(getContext().getOptionalResources()); + } + } Candidates getMultipleCardCandidates() { return m_multipleCardCandidates; @@ -279,28 +298,52 @@ List getDynamicCandidates() { return m_dynamicCandidates; } - public boolean isValidOnDemandResource(Resource fragment) { - Boolean valid = m_validOnDemandResources.get(fragment); + public boolean isValidRelatedResource(Resource resource) { + Boolean valid = m_validRelatedResources.get(resource); if (valid == null) { - // Mark this resource as a valid on demand resource - m_validOnDemandResources.put(fragment, Boolean.TRUE); + // Mark this resource as a valid related resource + m_validRelatedResources.put(resource, Boolean.TRUE); valid = Boolean.TRUE; } return valid; } - public boolean invalidateOnDemandResource(Resource faultyResource) { - Boolean valid = m_validOnDemandResources.get(faultyResource); + public boolean invalidateRelatedResource(Resource faultyResource) { + Boolean valid = m_validRelatedResources.get(faultyResource); if (valid != null && valid) { - // This was an ondemand resource. + // This was related resource. // Invalidate it and try again. - m_validOnDemandResources.put(faultyResource, Boolean.FALSE); + m_validRelatedResources.put(faultyResource, Boolean.FALSE); return true; } return false; } + + public Collection getRelatedResources(Resource resource) { + Collection related = m_relatedResources.get(resource); + return related == null ? Collections. emptyList() : related; + } + + public void setRelatedResources(Resource resource, Collection related) { + m_relatedResources.put(resource, related); + } + + @Override + public void run() { + m_isCancelled = new CancellationException(); + } + + boolean isCancelled() { + return m_isCancelled != null; + } + + void checkForCancel() throws ResolutionException { + if (isCancelled()) { + throw new ResolutionException("Resolver operation has been cancelled.", m_isCancelled, null); + } + } } public ResolverImpl(Logger logger) @@ -371,13 +414,12 @@ public Void run() { public Map> resolve(ResolveContext rc, Executor executor) throws ResolutionException { - ResolveSession session = new ResolveSession(rc, executor, null, null, null); + ResolveSession session = ResolveSession.createSession(rc, executor, null, null, null); return doResolve(session); } private Map> doResolve(ResolveSession session) throws ResolutionException { Map> wireMap = new HashMap>(); - boolean retry; do { @@ -391,6 +433,7 @@ private Map> doResolve(ResolveSession session) throws Resol Map faultyResources = new HashMap(); Candidates allCandidates = findValidCandidates(session, faultyResources); + session.checkForCancel(); // If there is a resolve exception, then determine if an // optionally resolved resource is to blame (typically a fragment). @@ -402,7 +445,7 @@ private Map> doResolve(ResolveSession session) throws Resol retry = (session.getOptionalResources().removeAll(resourceKeys)); for (Resource faultyResource : resourceKeys) { - if (session.invalidateOnDemandResource(faultyResource)) + if (session.invalidateRelatedResource(faultyResource)) { retry = true; } @@ -430,8 +473,7 @@ private Map> doResolve(ResolveSession session) throws Resol } if (session.isDynamic() ) { - wireMap = populateDynamicWireMap(session.getContext(), - session.getDynamicHost(), session.getDynamicRequirement(), + wireMap = populateDynamicWireMap(session, wireMap, allCandidates); } else @@ -442,7 +484,7 @@ private Map> doResolve(ResolveSession session) throws Resol { wireMap = populateWireMap( - session.getContext(), allCandidates.getWrappedHost(resource), + session, allCandidates.getWrappedHost(resource), wireMap, allCandidates); } } @@ -549,7 +591,7 @@ else if (faultyResources.size() > currentFaultyResources.size()) } } } - while (session.getCurrentError() != null); + while (!session.isCancelled() && session.getCurrentError() != null); return allCandidates; } @@ -577,6 +619,9 @@ private ResolutionError checkConsistency( rethrow = checkPackageSpaceConsistency( session, entry.getValue(), allCandidates, session.isDynamic(), resourcePkgMap, resultCache); + if (session.isCancelled()) { + return null; + } if (rethrow != null) { Resource faultyResource = entry.getKey(); @@ -598,43 +643,18 @@ private ResolutionError checkConsistency( return error; } - /** - * Resolves a dynamic requirement for the specified host resource using the - * specified {@link ResolveContext}. The dynamic requirement may contain - * wild cards in its filter for the package name. The matching candidates - * are used to resolve the requirement and the resolve context is not asked - * to find providers for the dynamic requirement. The host resource is - * expected to not be a fragment, to already be resolved and have an - * existing wiring provided by the resolve context. - *

- * This operation may resolve additional resources in order to resolve the - * dynamic requirement. The returned map will contain entries for each - * resource that got resolved in addition to the specified host resource. - * The wire list for the host resource will only contain a single wire which - * is for the dynamic requirement. - * - * @param rc the resolve context - * @param host the hosting resource - * @param dynamicReq the dynamic requirement - * @param matches a list of matching capabilities - * @return The new resources and wires required to satisfy the specified - * dynamic requirement. The returned map is the property of the caller and - * can be modified by the caller. - * @throws ResolutionException - */ - public Map> resolve( - ResolveContext rc, Resource host, Requirement dynamicReq, - List matches) - throws ResolutionException + public Map> resolveDynamic(ResolveContext context, + Wiring hostWiring, Requirement dynamicRequirement) + throws ResolutionException { + Resource host = hostWiring.getResource(); + List matches = context.findProviders(dynamicRequirement); // We can only create a dynamic import if the following // conditions are met: - // 1. The specified resource is resolved. - // 2. The package in question is not already imported. - // 3. The package in question is not accessible via require-bundle. - // 4. The package in question is not exported by the resource. - // 5. The package in question matches a dynamic import of the resource. - if (!matches.isEmpty() && rc.getWirings().containsKey(host)) + // 1. The package in question is not already imported. + // 2. The package in question is not accessible via require-bundle. + // 3. The package in question is not exported by the resource. + if (!matches.isEmpty()) { // Make sure all matching candidates are packages. for (Capability cap : matches) @@ -645,11 +665,11 @@ public Map> resolve( "Matching candidate does not provide a package name."); } } - ResolveSession session = new ResolveSession(rc, new DumbExecutor(), host, dynamicReq, matches); + ResolveSession session = ResolveSession.createSession(context, new DumbExecutor(), host, dynamicRequirement, matches); return doResolve(session); } - return Collections.emptyMap(); + throw new Candidates.MissingRequirementError(dynamicRequirement).toException(); } private static List getWireCandidates(ResolveSession session, Candidates allCandidates, Resource resource) @@ -1551,6 +1571,9 @@ else if (!sourceBlame.m_cap.getResource().equals(blame.m_cap.getResource())) rethrow = checkPackageSpaceConsistency( session, cap.getResource(), allCandidates, false, resourcePkgMap, resultCache); + if (session.isCancelled()) { + return null; + } if (rethrow != null) { // If the lower level check didn't create any permutations, @@ -1782,11 +1805,11 @@ private static Requirement getDeclaredRequirement(Requirement r) } private static Map> populateWireMap( - ResolveContext rc, Resource resource, + ResolveSession session, Resource resource, Map> wireMap, Candidates allCandidates) { Resource unwrappedResource = getDeclaredResource(resource); - if (!rc.getWirings().containsKey(unwrappedResource) + if (!session.getContext().getWirings().containsKey(unwrappedResource) && !wireMap.containsKey(unwrappedResource)) { wireMap.put(unwrappedResource, Collections.emptyList()); @@ -1809,7 +1832,7 @@ private static Map> populateWireMap( || !resource.equals(cand.getResource())) { // Populate wires for the candidate - populateWireMap(rc, cand.getResource(), + populateWireMap(session, cand.getResource(), wireMap, allCandidates); Resource provider; @@ -1889,7 +1912,7 @@ else if (req.getNamespace().equals(BundleNamespace.BUNDLE_NAMESPACE)) // Otherwise, if the fragment isn't already resolved and // this is the first time we are seeing it, then create // a wire for the non-payload requirement. - else if (!rc.getWirings().containsKey(fragment) + else if (!session.getContext().getWirings().containsKey(fragment) && !wireMap.containsKey(fragment)) { Wire wire = createWire(req, allCandidates); @@ -1905,6 +1928,12 @@ else if (!rc.getWirings().containsKey(fragment) wireMap.put(fragment, fragmentWires); } } + // now make sure any related resources are populated + for (Resource related : session.getRelatedResources(unwrappedResource)) { + if (allCandidates.isPopulated(related)) { + populateWireMap(session, related, wireMap, allCandidates); + } + } } return wireMap; @@ -1939,31 +1968,31 @@ private static boolean isPayload(Requirement fragmentReq) } private static Map> populateDynamicWireMap( - ResolveContext rc, Resource resource, Requirement dynReq, - Map> wireMap, Candidates allCandidates) + ResolveSession session, Map> wireMap, Candidates allCandidates) { - wireMap.put(resource, Collections.emptyList()); + wireMap.put(session.getDynamicHost(), Collections.emptyList()); List packageWires = new ArrayList(); // Get the candidates for the current dynamic requirement. // Record the dynamic candidate. - Capability dynCand = allCandidates.getFirstCandidate(dynReq); + Capability dynCand = allCandidates.getFirstCandidate(session.getDynamicRequirement()); - if (!rc.getWirings().containsKey(dynCand.getResource())) + if (!session.getContext().getWirings().containsKey(dynCand.getResource())) { - populateWireMap(rc, dynCand.getResource(), + populateWireMap(session, dynCand.getResource(), wireMap, allCandidates); } packageWires.add( new WireImpl( - resource, - dynReq, + session.getDynamicHost(), + session.getDynamicRequirement(), getDeclaredResource(dynCand.getResource()), getDeclaredCapability(dynCand))); - wireMap.put(resource, packageWires); + wireMap.put(session.getDynamicHost(), packageWires); return wireMap; } diff --git a/osgi-r7/resolver/src/main/java/org/osgi/service/resolver/HostedCapability.java b/osgi-r7/resolver/src/main/java/org/osgi/service/resolver/HostedCapability.java index 3c176de3592..f0db92eddda 100644 --- a/osgi-r7/resolver/src/main/java/org/osgi/service/resolver/HostedCapability.java +++ b/osgi-r7/resolver/src/main/java/org/osgi/service/resolver/HostedCapability.java @@ -1,5 +1,5 @@ /* - * Copyright (c) OSGi Alliance (2012). All Rights Reserved. + * Copyright (c) OSGi Alliance (2012, 2015). All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.osgi.service.resolver; +import org.osgi.annotation.versioning.ProviderType; import org.osgi.resource.Capability; import org.osgi.resource.Resource; @@ -32,9 +33,9 @@ * capability can actually be hosted multiple times. * * @ThreadSafe - * @noimplement - * @version $Id: db698baa07e2ee8b5467871239adb5f0806dc183 $ + * @author $Id: e6a6917f6cdb5021a7a5cf858a08a98677183606 $ */ +@ProviderType public interface HostedCapability extends Capability { /** @@ -42,6 +43,7 @@ public interface HostedCapability extends Capability { * * @return The Resource that hosts this Capability. */ + @Override Resource getResource(); /** diff --git a/osgi-r7/resolver/src/main/java/org/osgi/service/resolver/ResolutionException.java b/osgi-r7/resolver/src/main/java/org/osgi/service/resolver/ResolutionException.java index a626fc9679d..9dd01a01805 100644 --- a/osgi-r7/resolver/src/main/java/org/osgi/service/resolver/ResolutionException.java +++ b/osgi-r7/resolver/src/main/java/org/osgi/service/resolver/ResolutionException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) OSGi Alliance (2011, 2012). All Rights Reserved. + * Copyright (c) OSGi Alliance (2011, 2013). All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,13 +34,13 @@ * Resolver implementations may extend this class to provide extra state * information about the reason for the resolution failure. * - * @version $Id: 42e5773e3b7e240673874329e5d9e705d0b698c5 $ + * @author $Id: 2bcc0ac8ebfaf169dd301493303d23ce613ac8b9 $ */ public class ResolutionException extends Exception { private static final long serialVersionUID = 1L; - private final Collection unresolvedRequirements; + private final transient Collection unresolvedRequirements; /** * Create a {@code ResolutionException} with the specified message, cause @@ -55,7 +55,7 @@ public class ResolutionException extends Exception { public ResolutionException(String message, Throwable cause, Collection unresolvedRequirements) { super(message, cause); if ((unresolvedRequirements == null) || unresolvedRequirements.isEmpty()) { - this.unresolvedRequirements = emptyCollection(); + this.unresolvedRequirements = null; } else { this.unresolvedRequirements = Collections.unmodifiableCollection(new ArrayList(unresolvedRequirements)); } @@ -68,7 +68,7 @@ public ResolutionException(String message, Throwable cause, Collection Collection emptyCollection() { + @SuppressWarnings("unchecked") + private static Collection emptyCollection() { return Collections.EMPTY_LIST; } @@ -95,9 +96,9 @@ private static Collection emptyCollection() { * * @return A collection of the unresolved requirements for this exception. * The returned collection may be empty if no unresolved - * requirements information is provided. + * requirements information is available. */ public Collection getUnresolvedRequirements() { - return unresolvedRequirements; + return (unresolvedRequirements != null) ? unresolvedRequirements : emptyCollection(); } } diff --git a/osgi-r7/resolver/src/main/java/org/osgi/service/resolver/ResolveContext.java b/osgi-r7/resolver/src/main/java/org/osgi/service/resolver/ResolveContext.java index 5dac45354fd..6539c364047 100644 --- a/osgi-r7/resolver/src/main/java/org/osgi/service/resolver/ResolveContext.java +++ b/osgi-r7/resolver/src/main/java/org/osgi/service/resolver/ResolveContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) OSGi Alliance (2011, 2012). All Rights Reserved. + * Copyright (c) OSGi Alliance (2011, 2016). All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,9 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.CancellationException; + +import org.osgi.annotation.versioning.ConsumerType; import org.osgi.resource.Capability; import org.osgi.resource.Requirement; import org.osgi.resource.Resource; @@ -29,7 +32,6 @@ * A resolve context provides resources, options and constraints to the * potential solution of a {@link Resolver#resolve(ResolveContext) resolve} * operation. - * *

* Resolve Contexts: *

    @@ -45,21 +47,21 @@ *
  • Filter requirements that are part of a resolve operation via the * {@link #isEffective(Requirement)}.
  • *
- * *

* A resolver may call the methods on the resolve context any number of times * during a resolve operation using any thread. Implementors should ensure that * this class is properly thread safe. - * *

- * Except for {@link #insertHostedCapability(List, HostedCapability)}, the - * resolve context methods must be idempotent. This means that resources - * must have constant capabilities and requirements and the resolve context must - * return a consistent set of capabilities, wires and effective requirements. + * Except for {@link #insertHostedCapability(List, HostedCapability)} and + * {@link #onCancel(Runnable)}, the resolve context methods must be + * idempotent. This means that resources must have constant capabilities + * and requirements and the resolve context must return a consistent set of + * capabilities, wires and effective requirements. * * @ThreadSafe - * @version $Id: f92eae32ab6fadb25e13d226458d6af50e8dcbba $ + * @author $Id: f70c6ec70d096ff04b9b4610add6bc25591f9a38 $ */ +@ConsumerType public abstract class ResolveContext { /** * Return the resources that must be resolved for this resolve context. @@ -67,8 +69,9 @@ public abstract class ResolveContext { *

* The default implementation returns an empty collection. * - * @return The resources that must be resolved for this resolve context. May - * be empty if there are no mandatory resources. + * @return A collection of the resources that must be resolved for this + * resolve context. May be empty if there are no mandatory + * resources. The returned collection may be unmodifiable. */ public Collection getMandatoryResources() { return emptyCollection(); @@ -82,14 +85,15 @@ public Collection getMandatoryResources() { *

* The default implementation returns an empty collection. * - * @return The resources that the resolver should attempt to resolve for - * this resolve context. May be empty if there are no mandatory - * resources. + * @return A collection of the resources that the resolver should attempt to + * resolve for this resolve context. May be empty if there are no + * optional resources. The returned collection may be unmodifiable. */ public Collection getOptionalResources() { return emptyCollection(); } + @SuppressWarnings("unchecked") private static Collection emptyCollection() { return Collections.EMPTY_LIST; } @@ -115,7 +119,7 @@ private static Collection emptyCollection() { * that must originate from an attached host. * *

- * Each returned Capability must match the given Requirement. This implies + * Each returned Capability must match the given Requirement. This means * that the filter in the Requirement must match as well as any namespace * specific directives. For example, the mandatory attributes for the * {@code osgi.wiring.package} namespace. @@ -181,4 +185,63 @@ private static Collection emptyCollection() { * unmodifiable. */ public abstract Map getWirings(); + + /** + * Find resources that are related to the given resource. + *

+ * The resolver attempts to resolve related resources during the current + * resolve operation. Failing to resolve one of the related resources will + * not result in a resolution exception unless the related resource is also + * a {@link #getMandatoryResources() mandatory} resource. + *

+ * The resolve context is asked to return related resources for each + * resource that is pulled into a resolve operation. This includes the + * {@link #getMandatoryResources() mandatory} and + * {@link #getOptionalResources() optional} resources and each related + * resource returned by this method. + *

+ * For example, a fragment can be considered a related resource for a host + * bundle. When a host is being resolved the resolve context will be asked + * if any related resources should be added to the resolve operation. The + * resolve context may decide that the potential fragments of the host + * should be resolved along with the host. + * + * @param resource The Resource that a resolver is attempting to find + * related resources for. Must not be {@code null}. + * @return A collection of the resources that the resolver should attempt to + * resolve for this resolve context. May be empty if there are no + * related resources. The returned collection may be unmodifiable. + * @since 1.1 + */ + public Collection findRelatedResources(Resource resource) { + return Collections.emptyList(); + } + + /** + * Registers a callback with the resolve context that is associated with the + * currently running resolve operation. The callback can be executed in + * order to cancel the currently running resolve operation. + *

+ * When a resolve operation begins, the resolver must call this method once + * and only once for the duration of the resolve operation and that call + * must happen before calling any other method on this resolve context. If + * the specified callback is executed then the resolver must cancel the + * currently running resolve operation and throw a + * {@link ResolutionException} with a cause of type + * {@link CancellationException}. + *

+ * The callback allows a resolve context to cancel a long running resolve + * operation that appears to be running endlessly or at risk of running out + * of resources. The resolve context may then decide to give up on resolve + * operation or attempt to try another resolve operation with a smaller set + * of resources which may allow the resolve operation to complete normally. + * + * @param callback the callback to execute in order to cancel the resolve + * operation + * @throws IllegalStateException if the resolver attempts to register more + * than one callback for a resolve operation + */ + public void onCancel(Runnable callback) { + // do nothing by default + } } diff --git a/osgi-r7/resolver/src/main/java/org/osgi/service/resolver/Resolver.java b/osgi-r7/resolver/src/main/java/org/osgi/service/resolver/Resolver.java index d451adeba2f..7aa8bc1f136 100644 --- a/osgi-r7/resolver/src/main/java/org/osgi/service/resolver/Resolver.java +++ b/osgi-r7/resolver/src/main/java/org/osgi/service/resolver/Resolver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) OSGi Alliance (2006, 2012). All Rights Reserved. + * Copyright (c) OSGi Alliance (2006, 2016). All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,17 +22,23 @@ import java.util.List; import java.util.Map; + +import org.osgi.annotation.versioning.ProviderType; +import org.osgi.framework.namespace.PackageNamespace; +import org.osgi.resource.Namespace; +import org.osgi.resource.Requirement; import org.osgi.resource.Resource; import org.osgi.resource.Wire; +import org.osgi.resource.Wiring; /** * A resolver service resolves the specified resources in the context supplied * by the caller. * * @ThreadSafe - * @noimplement - * @version $Id: dfb89b8d09af62ecf62321b80d7e2310512f27a1 $ + * @author $Id: 555f3cd1543e7a2c3492a71d74dcf728668265b6 $ */ +@ProviderType public interface Resolver { /** * Resolve the specified resolve context and return any new resources and @@ -69,4 +75,60 @@ public interface Resolver { * @throws ResolutionException If the resolution cannot be satisfied. */ Map> resolve(ResolveContext context) throws ResolutionException; + + /** + * Resolves a given dynamic requirement dynamically for the given host + * wiring using the given resolve context and return any new resources and + * wires to the caller. + *

+ * The requirement must be a {@link Wiring#getResourceRequirements(String) + * requirement} of the wiring and must use the + * {@link PackageNamespace#PACKAGE_NAMESPACE package} namespace. + *

+ * The resolve context is not asked for + * {@link ResolveContext#getMandatoryResources() mandatory} resources or for + * {@link ResolveContext#getMandatoryResources() optional} resources. The + * resolve context is asked to + * {@link ResolveContext#findProviders(Requirement) find providers} for the + * given requirement. The matching {@link PackageNamespace#PACKAGE_NAMESPACE + * package} capabilities returned by the resolve context must not have a + * {@link PackageNamespace#PACKAGE_NAMESPACE osgi.wiring.package} attribute + * equal to a {@link PackageNamespace#PACKAGE_NAMESPACE package} capability + * already {@link Wiring#getRequiredResourceWires(String) wired to} by the + * wiring or equal a {@link PackageNamespace#PACKAGE_NAMESPACE package} + * capability {@link Wiring#getResourceCapabilities(String) provided} by the + * wiring. The resolve context may be requested to + * {@link ResolveContext#findProviders(Requirement) find providers} for + * other requirements in order to resolve the resources that provide the + * matching capabilities to the given requirement. + *

+ * If the requirement {@link Namespace#REQUIREMENT_CARDINALITY_DIRECTIVE + * cardinality} is not {@link Namespace#CARDINALITY_MULTIPLE multiple} then + * no new wire must be created if the + * {@link Wiring#getRequiredResourceWires(String) wires} of the wiring + * already contain a wire that uses the {@link Wire#getRequirement() + * requirement} + *

+ * This operation may resolve additional resources in order to resolve the + * dynamic requirement. The returned map will contain entries for each + * resource that got resolved in addition to the specified wiring + * {@link Wiring#getResource() resource}. The wire list for the wiring + * resource will only contain one wire which is for the dynamic requirement. + * + * @param context The resolve context for the resolve operation. Must not be + * {@code null}. + * @param hostWiring The wiring with the dynamic + * {@link Wiring#getResourceRequirements(String) requirement}. + * Must not be {@code null}. + * @param dynamicRequirement The dynamic requirement. Must not be + * {@code null}. + * @return The new resources and wires required to satisfy the specified + * dynamic requirement. The returned map is the property of the + * caller and can be modified by the caller. If no new wires were + * created then a ResolutionException is thrown. + * @throws ResolutionException if the dynamic requirement cannot be resolved + */ + public Map> resolveDynamic(ResolveContext context, + Wiring hostWiring, Requirement dynamicRequirement) + throws ResolutionException; } diff --git a/osgi-r7/resolver/src/main/java/org/osgi/service/resolver/package-info.java b/osgi-r7/resolver/src/main/java/org/osgi/service/resolver/package-info.java index d361a48eeee..88ebda3365a 100644 --- a/osgi-r7/resolver/src/main/java/org/osgi/service/resolver/package-info.java +++ b/osgi-r7/resolver/src/main/java/org/osgi/service/resolver/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) OSGi Alliance (2010, 2012). All Rights Reserved. + * Copyright (c) OSGi Alliance (2010, 2016). All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,25 +15,26 @@ */ /** - * Resolver Service Package Version 1.0. - * + * Resolver Service Package Version 1.1. *

* Bundles wishing to use this package must list the package in the * Import-Package header of the bundle's manifest. This package has two types of * users: the consumers that use the API in this package and the providers that * implement the API in this package. - * *

* Example import for consumers using the API in this package: *

- * {@code Import-Package: org.osgi.service.resolver; version="[1.0,2.0)"} + * {@code Import-Package: org.osgi.service.resolver; version="[1.1,2.0)"} *

* Example import for providers implementing the API in this package: *

- * {@code Import-Package: org.osgi.service.resolver; version="[1.0,1.1)"} + * {@code Import-Package: org.osgi.service.resolver; version="[1.1,1.2)"} * - * @version $Id: db1706d83ca104187f77cb1feb7cf52b92b3740d $ + * @author $Id: 6ac829e72173e50ab58bedd13edd66a1b50e58bd $ */ +@Version("1.1") package org.osgi.service.resolver; +import org.osgi.annotation.versioning.Version; + diff --git a/osgi-r7/resolver/src/main/java/org/osgi/service/resolver/packageinfo b/osgi-r7/resolver/src/main/java/org/osgi/service/resolver/packageinfo deleted file mode 100644 index 7c8de0324fd..00000000000 --- a/osgi-r7/resolver/src/main/java/org/osgi/service/resolver/packageinfo +++ /dev/null @@ -1 +0,0 @@ -version 1.0 diff --git a/osgi-r7/resolver/src/test/java/org/apache/felix/resolver/test/ResolverTest.java b/osgi-r7/resolver/src/test/java/org/apache/felix/resolver/test/ResolverTest.java index a2ce13f6ffe..25379aa5bf8 100644 --- a/osgi-r7/resolver/src/test/java/org/apache/felix/resolver/test/ResolverTest.java +++ b/osgi-r7/resolver/src/test/java/org/apache/felix/resolver/test/ResolverTest.java @@ -475,7 +475,7 @@ public void testScenario10() throws Exception List caps = new ArrayList(); caps.add(f1_pkgCap); - Map> wireMap = resolver.resolve(rci, b1, b_pkgReq1, caps); + Map> wireMap = resolver.resolveDynamic(rci, wirings.get(b1), b_pkgReq1); assertEquals(1, wireMap.size()); List wiresB = wireMap.get(b1); @@ -526,7 +526,7 @@ public void testScenario11() throws Exception List caps = new ArrayList(); caps.add(f1_pkgCap); try { - resolver.resolve(rci, b1, b_pkgReq1, caps); + resolver.resolveDynamic(rci, wirings.get(b1), b_pkgReq1); fail("Should fail to dynamic requirement to fragment when host is resolved already."); } catch (ResolutionException e) { // expected @@ -537,7 +537,7 @@ public void testScenario11() throws Exception wirings.remove(a1); caps.clear(); caps.add(f1_pkgCap); - Map> wireMap = resolver.resolve(rci, b1, b_pkgReq1, caps); + Map> wireMap = resolver.resolveDynamic(rci, wirings.get(b1), b_pkgReq1); assertEquals(3, wireMap.size()); List wiresB = wireMap.get(b1); @@ -588,7 +588,7 @@ public void testScenario12() throws Exception List caps = new ArrayList(); caps.add(c_pkgCap); - Map> wireMap = resolver.resolve(rci, b1, b_pkgReq1, caps); + Map> wireMap = resolver.resolveDynamic(rci, wirings.get(b1), b_pkgReq1); assertEquals(0, wireMap.size()); } diff --git a/osgi-r7/resolver/src/test/java/org/apache/felix/resolver/test/util/IterativeResolver.java b/osgi-r7/resolver/src/test/java/org/apache/felix/resolver/test/util/IterativeResolver.java index 717e32580bb..d18e03aa8ed 100644 --- a/osgi-r7/resolver/src/test/java/org/apache/felix/resolver/test/util/IterativeResolver.java +++ b/osgi-r7/resolver/src/test/java/org/apache/felix/resolver/test/util/IterativeResolver.java @@ -192,4 +192,11 @@ public Resource getResource() { } } + @Override + public Map> resolveDynamic(ResolveContext context, Wiring hostWiring, Requirement dynamicRequirement) + throws ResolutionException + { + return null; + } + }