The module layer is responsible for the resolve operation that wires requirements (Import-Package, Require-Bundle etc.) to capabilities (Export-Package, Bundle-SymbolicName/Bundle-Version etc.). The resolve operation is designed to work stand-alone but in certain use cases it is important to be able to influence the resolver's decisions. This specification defines a Resolver Hook Factory service that allows third party bundles to influence the resolve operation. Primary use cases for this hook are scoping of groups of bundles. However, the hooks also allows bundle to find out about, and control, the resolve operation for other purposes.
-
Reduction - Allow third party bundles to remove capabilities from a set of capabilities that matches a requirement.
-
Complete - Support all built-in namespaces as well as the generic capability and requirement headers.
-
Singletons - Allow third party bundles to remove candidates from a set of singleton capabilities before the resolver selects one.
-
Secure - Support full security but allow operation without security present.
-
Resolver Hook Factory - The service registered by a bundle that wants to control the resolve operation. This service is used to create an instance of the Resolver Hook for each resolve operation.
-
Resolver Hook - Is created by the Resolver Hook Factory service for each resolver process. Is consulted to reduce candidate capabilities for resolving and selecting singletons.
-
Client - A bundle that is considered during the resolve operation.
-
Handler - A bundle that registers a Resolver Hook Factory service for influencing the resolve operation.
-
Bundle Capability - A capability represents a feature of a bundle described with attributes and directives defined in a namespace. Some namespaces are built-in to the OSGi framework, others are generic.
-
Bundle Requirement - A requirement represents a need from a bundle described as a filter on the attributes of a Bundle Capability.
-
Provider - A bundle that provides a Bundle Capability.
-
Consumer - A bundle that requires a Bundle Capability
-
Resolver - The internal framework machinery that resolves requirements to providers with matching capabilities.
A handler bundle that needs to manage the wiring between bundles must register a Resolver Hook Factory service. For each resolve operation the Resolver needs to perform, the framework asks each Resolver Hook Factory service for a new Resolver Hook specific for the operation. During the resolve operation, the Resolver will allow the Resolver Hooks to remove candidate solutions and assist in selecting singletons.
The Resolver is triggered by activity in the OSGi framework.
Calling certain methods on a bundle when a bundle is in the
INSTALLED
state will cause the framework to begin a
resolve operation in order to resolve the bundle.
Other API can also trigger a resolver. Frameworks can resolve on a per
bundle basis or they can resolve a number of bundles atomically in one
operation. The bundles that trigger a resolve operation are called the
trigger bundles. The trigger bundles can be defined
by the following cases:
-
Root Bundle - Calling certain methods on a bundle when a bundle is in the
INSTALLED
state will cause the framework to begin a resolve operation in order to resolve the bundle. In general, a bundle needs to be resolved when its class loader is needed. The following Bundle methods will start a resolve operation when the subject is not yet resolved:-
start
-
loadClass
-
findEntries
-
getResource
-
getResources
-
-
Resolve Bundles - The set of bundle revisions of the unresolved bundles given as argument, or their default when
null
is used, to the Framework WiringresolveBundles
method. See Using the Wiring API. -
Refresh Bundles - A refresh operation will have to re-resolve the bundles that will be unresolved in the refresh. The trigger bundles are then the bundle revisions of the dependency closure, which is described in Refreshing.
-
Dynamic Import - A Dynamic Import can require the framework to start a resolve operation.
-
Other - The Resolver Hook is a service so other parties can to start a resolver operation to run what-if scenarios or for other purposes.
Various types of resolve operations can be initiated:
-
Static - A static bundle resolve operation. This resolve operation is necessary any time one or more bundles transitions from the
INSTALLED
state to theRESOLVED
state. During this resolve operation the framework attempts to resolve static requirements specified by the bundles being resolved. -
Dynamic - A dynamic import must be resolved.
The resolve operation is not limited to the trigger bundles only,
they just provide the root bundles that must be resolved. The Resolver
is free to consider any present bundle to provide the required
capabilities. If such bundles are in the INSTALLED
state
then they can also become a candidate to be resolved. The resolver is
also free to use bundles that are actually not installed in the
framework, for example for what-if scenarios.
Once the resolver is triggered, it must prepare the Handlers to participate in the resolve operation. A Handler is an active bundle that needs to participate in the resolve operation, there can be multiple Handlers in a framework. A Handler must register a Resolver Hook Factory service. This service is the interface between a Handler bundle, a bundle that will handle some aspects of the resolve operation, and the Resolver.
To prepare the Handlers, the Resolver must request a new Resolver
Hook from each of the registered Resolver Hook Factory services with the
begin(Collection) method. The parameter is the set of trigger
bundles. The Handler is expected to create a new ResolverHook object for each call. If null
is
returned then the Handler abstains from participation. A Resolver Hook
Factory must be thread-safe and allow the creation of independent
Resolver Hook objects that can be active on multiple threads.
A Resolver Hook is created for a single atomic resolve operation
and does not have to be thread safe. The Resolver must ensure that
access to the Resolver Hook is serialized, that is, the can only be
active from a single thread. The ResolverHook
object is
called multiple times during a resolve operation to influence the
outcome of a resolve operation. The following operations are
provided:
-
filterResolvable(Collection) - Removes bundles that are candidates for resolving so they do not resolve in the current operation. Provides an easy way to exclude bundles.
-
filterMatches(BundleRequirement,Collection) - Remove matching capabilities from the candidate capabilities. This effectively hides capabilities for certain requirers.
-
filterSingletonCollisions(BundleCapability,Collection) - Remove potentially conflicting singletons from the collection. This provides the possibility to resolve a singleton multiple times in different groups.
A Resolver Hook can influence the outcome of a resolve operation by removing entries from shrinkable collections. A shrinkable collection is a collection that can only shrink. The Handler can remove entries from the collection but it cannot add an entry to it. Any attempt to add to the collection must result in throwing an Unsupported Operation Exception. All collections in the Resolver Hook API are shrinkable collections.
The Resolver Hook Factory services begin(Collection) method is called in the ranking order, see Service Ranking Order. This is the same order used for calling the resulting Resolver Hooks. The Resolver Hook end() method notifies the Handler that the resolve operation has finished. The framework must not hold on to this Resolver Hook instance after the end() method has been called.
There are use cases where a bundle that is being installed should not be allowed to resolve until some activity has taken place. Sometimes certain bundles should never resolve. For example, there are byte code weaving scenarios where a bundle is used as the source but a synthetic bundle with the woven code provides the actual classes. The source bundle should then not resolve. The filterResolvable(Collection) method can be used to ensure that certain bundles are never resolved. All the given Bundle Revisions are unresolved. The Handler can look at the given collection and remove any bundles it wants to prevent being resolved in this resolve operation.
The set of bundles passed will contain the trigger bundles. If a Handler removes one of the trigger bundles the resolve of the removed bundle will not succeed and fail the call to the method that triggered the resolve operation.
The framework can pass an empty collection of resolvable bundle
revisions, this could for example happen while resolving a dynamic
import. An empty collection indicates that the framework will not cause
any bundles to transition from INSTALLED
to
RESOLVED
during a dynamic import package resolving.
For example, a Handler wants to ensure certain bundles are not resolved, then it can do:
public class UnresolveHandler implements ResolverHook{
Set<BundleRevision> neverResolve = ... ;
public void filterResolvable(
Collection<BundleRevision> toBeResolved ) {
toBeResolved.removeAll( neverResolve );
}
... other methods
}
The filterMatches(BundleRequirement,Collection) method is used to remove capabilities for consideration for a specific requirer. The Handler receives the Bundle Requirement and the set of candidates that already match the requirement. The Handler can now remove any candidates that are not suitable. Removing the capability will prevent the requirement from getting wired to the capability. If the Bundle Requirement is declared in a fragment then the host is not knowable.
For example, a Handler wants to ensure that a set of bundles in a group are only wired to a limited set of infra-structure bundles and each other. This could be implemented as follows:
public class GroupHandler implements ResolverHook{
Map<Bundle,Set<Bundle>> groups = ...;
Set<Bundle> system = ... ;
public void filterMatches( BundleRequirement r,
Collection<BundleCapability> candidates ) {
Set<Bundle> group = groups.get(r.getRevision().getBundle());
if ( group == null )
return; // not in a group
for ( Iterator<BundleCapability> i = candidates.iterator();
i.hasNext(); ) {
BundleCapability candidate = i.next();
Bundle other = candidate.getRevision().getBundle();
if ( group.contains(other) ||
system.contains(other) )
continue;
i.remove(); // not system, not in the same group
}
}
... other methods
}
Certain namespaces provide a singleton
directive. For
example, the osgi.wiring.bundle
namespace defines that a
bundle can be singleton, meaning that only one such bundle with a given
symbolic name can be resolved. The purpose is to ensure that a bundle
that needs exclusiveness gets this.
In certain scenarios it is necessary to limit the singleton constraint to a group of bundles instead of the whole framework. One of the primary use cases of the Resolver Hooks is to allow scoping of bundles. Some Handlers can interpret the singleton constraints as to apply to the group, not the whole framework. For this purpose, the Resolver Hook API allows the Handler to influence which bundle revision is selected for the singleton with the filterSingletonCollisions(BundleCapability,Collection) method.
The first parameter is the capability that is under consideration
by the resolver, called the viewpoint capability.
The resolver needs to find out what other capabilities can
collide with the viewpoint. A collision takes place
when multiple bundles with the same symbolic name and singleton
directive set to true
can potentially be resolved at the
same time. For example, a Handler implements a grouping model. A
singleton is therefore only valid for the bundles in this group. A
Handler must therefore be able to indicate which bundles can collide.
This model is asymmetric. If a group has for example outer and inner
bundles, then inner bundles can collide with outer bundles but not vice
versa.
The second parameters of the filterSingletonCollisions(BundleCapability,Collection) method is a set of capabilities called the candidates. The Handler can shrink this collection by removing capabilities. Removing a capability from the list of collision candidates will effectively hide the collision candidate from the target singleton bundle. This will allow the target singleton bundle to resolve regardless of the resolving state of the collision candidate.
If S
is the group of infrastructure bundles (acting
as an outer bundles) and a non-infrastructure group is A
,
then the following cases exist:
S S collide, leave in set
A A collide, leave in set
A !A remove from set, not visible
A S collide, leave in set
S A do not collide, remove from set
The following example implements this strategy:
public class GroupHandler implements ResolverHook{
Map<Bundle,Set<Bundle>> groups = ...;
public void filterSingletonCollisions( BundleCapability c,
Collection<BundleCapability> candidates ) {
Set<Bundle> group = groups.get(c.getRevision().getBundle());
for ( Iterator<BundleCapability> i = candidates.iterator();
i.hasNext(); ) {
BundleCapability candidate = i.next();
Bundle other = candidate.getRevision().getBundle();
Set<Bundle> otherGroup = groups.get(other);
if ( group == otherGroup || otherGroup == null ) // Samegroup
continue;
i.remove(); // not system, not in the same group
}
}
... other methods
}
The framework can call this method multiple times for the same singleton capability. For example, as a first pass a framework may want to determine if a singleton bundle is resolvable first based on the presence of other already resolved singleton capabilities. Later the framework may call this method again to determine which singleton capabilities from unresolved bundles to allow to resolve.
The following steps outline the way a framework uses the resolver hooks during a resolve operation. Any callbacks to the hook services must be done in a privileged block, see Privileged Callbacks.
-
Collect a snapshot of registered Resolver Hook Factory services that will be called during the current resolve operation. If a Resolver Hook Factory contained in the snapshot unregisters then the resolve must fail, see Failures. Each registered Resolver Hook Factory service in the snapshot will be obtained by the framework through the system's bundle context.
-
For each Resolver Hook Factory in the snapshot, in ranking order, call the begin(Collection) method to inform the Handler about the begin of a new resolve operation. This method must return a Resolver Hook. If this hook is not null, must be added to list
H
. If a corresponding Resolver Hook Factory service is unregistered then the Resolver Hook must be removed fromH
. -
Determine the collection of unresolved bundle revisions that may be considered for resolving during the current resolve operation and place each of the bundle revisions in a shrinkable collection
U(nresolved)
.-
For each active Resolver Hook in
H(ooks)
, call the filterResolvable(Collection) method withU
. The Handler can remove any candidate that should not be resolved.
U
now contains all the unresolved bundle revisions that potentially could be resolved during this resolve operation. Any bundle revisions that were removed by Handlers must not resolve in this operation.This step can be repeated multiple times interspersed with other calls except to the
begin
andend
methods. -
-
S = {}
-
For each bundle revision
B
inU
that represents a singleton capability:-
Determine the collection of available capabilities that have a namespace of
osgi.wiring.bundle
, are singletons, and have the same symbolic name as the singleton bundle revisionB
and place each of the matching capabilities into a shrinkable collectionS
. -
Remove the
osgi.wiring.bundle
capability provided by the bundle revision B fromS
. A singleton bundle cannot collide with itself. -
For each resolver hook call the filterSingletonCollisions(BundleCapability,Collection) method with the
osgi.wiring.bundle
capability provided by bundle revisionB
andS
.
S
now contains all the singletonosgi.wiring.bundle
capabilities that can influence the ability of bundle revisionB
to resolve.This step can be repeated multiple times interspersed with other calls except to the
begin
andend
methods. -
-
During a resolve operation the Resolver can resolve any or all bundles contained in
U
. For each bundle revisionB
inU
which the Resolver attempts to resolve the following steps must be followed:-
For each requirement
R(equirement)
specified by bundle revisionB
, determine the collection of capabilities that satisfy the requirementR
and place each matching capability into a shrinkable collectionC(apabilities)
. A capability is considered to satisfy a particular requirement if its attributes match the requirement's filter and the requirer bundle has permission to access the capability. -
For each Resolver Hook in
H
, call the filterMatches(BundleRequirement,Collection) method with the Bundle RequirementR
and the shrinkable collectionC
.
C
now contains all the capabilities that can be used to satisfy the requirementR
. Any other capabilities that got removed fromC
must not be used to satisfy requirement R.This step can be repeated multiple times interspersed with other calls except
begin
andend
. -
-
For each Resolver Hook in
H
-
Call the
end
method to inform the Handler about a resolve operation ending.
-
-
For each Resolve Hook still in
H
, ensure that the Resolver Hook is no longer referenced.
In cases where the a shrinkable collection becomes empty the framework must continue calling the remaining hooks because these hooks can also be used to trace progress.
The above steps are meant to illustrate how the Resolve Hooks are
used by the Resolver, they are not normative. The nature of the resolve
operation and the Resolver algorithm can require back tracking by the
Resolver implementation. It is therefore acceptable for the Resolver to
call the Resolver Hook multiple times with similar or identical parameters
during a single resolve operation. This is true for all methods except the
begin
and end
methods that are only called once
during a resolve operation.
A Resolver Hook must always return the same answer for the same set of inputs, that is, it must be stable.
Resolver hooks are system level components. Handlers must be careful not to create an unresolvable state which is very hard for a developer or a provisioner to diagnose. Resolver Hooks also must not be allowed to start another resolve operation, for example by starting a bundle or resolving bundles. The framework must detect this and throw an Illegal State Exception.
In cases where a Bundle Exception can be thrown, such as the
Bundle start
method, the Illegal State Exception must be
the cause of the Bundle Exception and the Bundle Exception must be of
type RESOLVE_ERROR
. In cases where an exception cannot be
propagated to a caller, for example during dynamic import resolving, a
Framework Event of type ERROR
must be published.
All hooks are treated as ordinary services, they can be used by the system bundle or other bundles.
If during the resolving anything goes wrong then the Resolver must abort the resolve operation, clean up and report an error.
If the begin(Collection) method successfully returns a ResolverHook, then the end() method must be called on that hook if it is still valid (its ResolverHookFactory is still registered) at the end of the Resolve. A ResolverHook can therefore safely allocate resources in the begin(Collection) method because it is guaranteed that it can deallocate them in the end() method.
The following are potential failures:
-
A Resolver Hook Factory used in a resolve operation is unregistered
-
A Resolver Hook throws an exception.
If the Resolver fails, it must throw a Bundle Exception to the
caller if possible. Otherwise it must publish a Framework Event of type
ERROR
.
The Resolver Hook Factory service described in this specification is a highly complex facility that requires great care in their implementation to maintain the Framework invariants. It is therefore important that in a secure system the permission to register these hooks is only given to privileged bundles.
In this case, the user of the Resolver Hook Factory service is the framework. Therefore, there is never a need to provide this service. Implementers of these hooks must have:
-
ServicePermission[..ResolverHookFactory,REGISTER]
for Event Listener Hook services.
Framework Resolver Hooks Package Version 1.0.
Bundles wishing to use this package must list the package in the Import-Package header of the bundle's manifest.
Example import for consumers using the API in this package:
Import-Package: org.osgi.framework.hooks.resolver; version="[1.0,2.0)"
-
ResolverHook
- OSGi Framework Resolver Hook instances are obtained from the OSGi Framework Resolver Hook Factory service. -
ResolverHookFactory
- OSGi Framework Resolver Hook Factory Service.
OSGi Framework Resolver Hook instances are obtained from the OSGi Framework Resolver Hook Factory service.
A Resolver Hook instance is called by the framework during a resolve process.
A resolver hook may influence the outcome of a resolve process by removing
entries from shrinkable collections that are passed to the hook during a
resolve process. A shrinkable collection is a Collection
that
supports all remove operations. Any other attempts to modify a shrinkable
collection will result in an UnsupportedOperationException
being
thrown.
The following steps outline the way a framework uses the resolver hooks during a resolve process.
-
Collect a snapshot of registered resolver hook factories that will be called during the current resolve process. Any hook factories registered after the snapshot is taken must not be called during the current resolve process. A resolver hook factory contained in the snapshot may become unregistered during the resolve process. The framework should handle this and stop calling the resolver hook instance provided by the unregistered hook factory and the current resolve process must fail. If possible, an exception must be thrown to the caller of the API which triggered the resolve process. In cases where the caller is not available a framework event of type error should be fired.
-
For each registered hook factory call the ResolverHookFactory.begin(Collection) method to inform the hooks about a resolve process beginning and to obtain a Resolver Hook instance that will be used for the duration of the resolve process.
-
Determine the collection of unresolved bundle revisions that may be considered for resolution during the current resolution process and place each of the bundle revisions in a shrinkable collection
Resolvable
. For each resolver hook call the filterResolvable(Collection) method with the shrinkable collectionResolvable
. -
The shrinkable collection
Resolvable
now contains all the unresolved bundle revisions that may end up as resolved at the end of the current resolve process. Any other bundle revisions that got removed from the shrinkable collectionResolvable
must not end up as resolved at the end of the current resolve process. -
For each bundle revision
B
left in the shrinkable collectionResolvable
and any bundle revisionB
which is currently resolved that represents a singleton bundle do the following:-
Determine the collection of available capabilities that have a namespace of osgi.identity, are singletons, and have the same symbolic name as the singleton bundle revision
B
and place each of the matching capabilities into a shrinkable collectionCollisions
. -
Remove the osgi.identity capability provided by bundle revision
B
from shrinkable collectionCollisions
. A singleton bundle cannot collide with itself. -
For each resolver hook call the filterSingletonCollisions(BundleCapability, Collection) with the osgi.identity capability provided by bundle revision
B
and the shrinkable collectionCollisions
-
The shrinkable collection
Collisions
now contains all singleton osgi.identity capabilities that can influence the ability of bundle revisionB
to resolve. -
If the bundle revision
B
is already resolved then any resolvable bundle revision contained in the collectionCollisions
is not allowed to resolve.
-
-
During a resolve process a framework is free to attempt to resolve any or all bundles contained in shrinkable collection
Resolvable
. For each bundle revisionB
left in the shrinkable collectionResolvable
which the framework attempts to resolve the following steps must be followed:-
For each requirement
R
specified by bundle revisionB
determine the collection of capabilities that satisfy (or match) the requirement and place each matching capability into a shrinkable collectionCandidates
. A capability is considered to match a particular requirement if its attributes satisfy a specified requirement and the requirer bundle has permission to access the capability. -
For each resolver hook call the filterMatches(BundleRequirement, Collection) with the requirement
R
and the shrinkable collectionCandidates
. -
The shrinkable collection
Candidates
now contains all the capabilities that may be used to satisfy the requirementR
. Any other capabilities that got removed from the shrinkable collectionCandidates
must not be used to satisfy requirementR
.
-
-
For each resolver hook call the end() method to inform the hooks about a resolve process ending.
In all cases, the order in which the resolver hooks are called is the reverse compareTo ordering of their Service References. That is, the service with the highest ranking number must be called first. In cases where a shrinkable collection becomes empty the framework is required to call the remaining registered hooks.
Resolver hooks are low level. Implementations of the resolver hook must be careful not to create an unresolvable state which is very hard for a developer or a provisioner to diagnose. Resolver hooks also must not be allowed to start another synchronous resolve process (e.g. by calling Bundle.start() or FrameworkWiring.resolveBundles(Collection) ). The framework must detect this and throw an IllegalStateException.
Not Thread-safe
This method is called once at the end of the resolve process. After the end method is called the resolve process has ended. The framework must not hold onto this resolver hook instance after end has been called.
the requirement to filter candidates for
a collection of candidates that match the requirement
Filter matches hook method. This method is called during the resolve process for the specified requirement. The collection of candidates match the specified requirement. This method can filter the collection of matching candidates by removing candidates from the collection. Removing a candidate will prevent the resolve process from choosing the removed candidate to satisfy the requirement.
All of the candidates will have the same namespace and will match the specified requirement.
If the Java Runtime Environment supports permissions then the collection of candidates will only contain candidates for which the requirer has permission to access.
the collection of resolvable candidates available during a resolve process.
Filter resolvable candidates hook method. This method may be called multiple times during a single resolve process. This method can filter the collection of candidates by removing potential candidates. Removing a candidate will prevent the candidate from resolving during the current resolve process.
the singleton involved in a resolve process
a collection of singleton collision candidates
Filter singleton collisions hook method. This method is called during the resolve process for the specified singleton. The specified singleton represents a singleton capability and the specified collection represent a collection of singleton capabilities which are considered collision candidates. The singleton capability and the collection of collision candidates must all use the same namespace.
Currently only capabilities with the namespace of osgi.wiring.bundle and osgi.identity can be singletons. The collision candidates will all have the same namespace, be singletons, and have the same symbolic name as the specified singleton capability.
In the future, capabilities in other namespaces may support the singleton concept. Hook implementations should be prepared to receive calls to this method for capabilities in namespaces other than osgi.wiring.bundle or osgi.identity.
This method can filter the list of collision candidates by removing potential collisions. Removing a collision candidate will allow the specified singleton to resolve regardless of the resolution state of the removed collision candidate.
OSGi Framework Resolver Hook Factory Service.
Bundles registering this service will be called by the framework during a bundle resolver process to obtain a resolver hook instance which will be used for the duration of a resolve process.
Thread-safe
an unmodifiable collection of bundles which triggered the resolve process. This collection may be empty if the collection of trigger bundles cannot be determined.
This method is called by the framework each time a resolve process begins to obtain a resolver hook instance. This resolver hook instance will be used for the duration of the resolve process. At the end of the resolve process the method ResolverHook.end() must be called by the framework and the framework must not hold any references of the resolver hook instance.
The triggers represent the collection of bundles which triggered the resolve process. This collection may be empty if the triggers cannot be determined by the framework. In most cases the triggers can easily be determined. Calling certain methods on bundle when a bundle is in the INSTALLED state will cause the framework to begin a resolve process in order to resolve the bundle. The following methods will start a resolve process in this case:
In such cases the collection will contain the single bundle which the framework is trying to resolve. Other cases will cause multiple bundles to be included in the trigger bundles collection. When resolveBundles is called the collection of triggers must include all the current bundle revisions for bundles passed to resolveBundles which are in the INSTALLED state.
When FrameworkWiring.refreshBundles(Collection, org.osgi.framework.FrameworkListener...) is called the collection of triggers is determined with the following steps:
-
If the collection of bundles passed is null then FrameworkWiring.getRemovalPendingBundles() is called to get the initial collection of bundles.
-
The equivalent of calling FrameworkWiring.getDependencyClosure(Collection) is called with the initial collection of bundles to get the dependency closure collection of the bundles being refreshed.
-
Remove any non-active bundles from the dependency closure collection.
-
For each bundle remaining in the dependency closure collection get the current bundle revision and add it to the collection of triggers.
As described above, a resolve process is typically initiated as a result of calling API that causes the framework to attempt to resolve one or more bundles. The framework is free to start a resolve process at any time for reasons other than calls to framework API. For example, a resolve process may be used by the framework for diagnostic purposes and result in no bundles actually becoming resolved at the end of the process. Resolver hook implementations must be prepared for resolve processes that are initiated for other reasons besides calls to framework API.
a resolver hook instance to be used for the duration of the
resolve process. A null
value may be returned which
indicates this resolver hook factory abstains from the resolve
process.