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;
+ }
+
}