A key aspect of the OSGi framework is managing the dependencies between the bundles. These dependencies are expressed as manifest headers that can be grouped into requirements and capabilities as defined in Resource API Specification. For example, an Export-Package clause is a capability and an Import-Package clause is a requirement. During the resolving phase the requirements are resolved to matching capabilities by creating a Bundle Wire. Some of the wires can influence how the classes are loaded from bundles during runtime.
This section outlines the API to introspect the wiring between the requirements and capabilities of resolved bundles.
-
Bundle Revision - Represents the class/resource container of an install or update (that is, the JAR, directory, or other form of archive). Each update creates a new Bundle Revision and an uninstall removes the Bundle Revisions. A Bundle Revision is modeled after a Resource.
-
Namespace - Bundle Requirements and Bundle Capabilities are defined in a namespace, namespaces define the semantics of the requirements and capabilities. The
osgi.wiring.bundle
,osgi.wiring.host
andosgi.wiring.package
from the Framework Namespaces are defined in Framework Namespaces Specification. -
Bundle Requirement - Represents a requirement header, either the Require-Capability header or any of the manifest headers referred to in the Framework Namespaces Specification that map to a requirement.
-
Bundle Capability - A quality of a Bundle Revision that is provided when the revision is installed. Implemented as a set of attributes that are part of a namespace. A Bundle Capability represents either the Provide-Capability manifest header clauses, or any headers defined in the OSGi namespaces that map to a capability.
-
Bundle Wiring - Created each time when a Bundle Revision is resolved for holding the wires to other Bundle Wirings as well as maintaining the run time state. Used by the framework to control class loading depending on the semantics of the OSGi namespaces.
-
Bundle Wire - Connects a Bundle Requirement to a Bundle Capability as well as the requirer Bundle Wiring and provider Bundle Wiring.
-
Framework Wiring - Provides access to manage and initiate refresh and resolving.
This section explains how the wiring API can be used without fully explaining all the concepts in depth. The next sections outline the formal specification.
The Bundle Context installBundle
method installs a
bundle and returns a Bundle
object. This install provides
the classes and resources in a JAR, directory or some other form, as an
environment. This resource is represented as a
Bundle Revision.
A Bundle Revision declares a number of Bundle Capabilities as well as a number of Bundle Requirements. A capability represents a set of attributes and a requirement is a filter on those attributes. For a requirement to be applicable to a capability, they must reside in the same namespace. The namespace groups requirements and capabilities and defines the semantics for a resolved requirement/capability pair. This pair is represented as a Bundle Wire.
Capabilities can be anything: certificates, screen size, the packages, the bundle itself or the capability to act as host for a fragment. Some capabilities and requirements are from the Provide-Capability and Require-Capability headers, others are defined by the OSGi headers defined in Module Layer, the namespaces for these OSGi specific headers are defined in Framework Namespaces Specification.
The framework wires the Bundle Requirements to Bundle Capabilities
during the resolving operation. The framework must resolve all the
requirements to matching capabilities according to the semantics of
their namespaces before it can declare a bundle to be resolved. For
generic namespaces it is sufficient to find a matching capability for
each requirement. However, for the OSGi namespaces additional rules are
implied. For example, the osgi.wiring.host
namespace
implies all the rules around OSGi fragment bundles.
Once a bundle is in the RESOLVED
state it gets a
Bundle Wiring, the Bundle Wiring represents the run
time state of the Bundle Revision. The Bundle Wiring holds the
Bundle Wires. A Bundle Wire ties a single Bundle
Requirement to a single Bundle Capability as well as tying the Bundle
Wiring that holds the requirement to the Bundle Wiring that holds the
capability. The Bundle Wires that flow from a Bundle Wiring's
Requirement to a capability are the required wires,
they can be obtained with getRequiredWires(String). Bundle Wires that come from a Bundle Wiring's
Capability to a requirement are the provided wires,
they can be obtained with getProvidedWires(String). The same requirements and capabilities can be
used in different wires.
Namespace rules can be complex. For example, in the case of fragments they imply that any capabilities from the fragment are actually available from its hosts. In the case of exported packages that are also imported the resolver can choose to pick either. These examples demonstrate that the resolver must be able to differentiate between the Bundle Revision's declared requirements and capabilities and the run time state, the Bundle Wiring, of the corresponding Bundle Revision. A Bundle Revision's Bundle Wiring therefore provides the actual run time requirements and capabilities as chosen by the resolver with the Bundle Wiring's getRequirements(String) and getCapabilities(String) methods. Any optional declared requirements that were not satisfied are not in the list of requirements. All dynamic requirements that can potentially be satisfied at run time are in this requirements list.
The BundleWiring
objects are therefore not
necessarily associated with the same Bundle Revisions that originate the
declared Bundle Requirement and the declared Bundle Capability. It is
therefore that the diagramming technique used in Figure 7.2 uses dotted lines for the Bundle Wiring connection.
That is, the connections from the Bundle Wire to the requirer
BundleWiring
object and to the provider
BundleWiring
object. It then uses solid lines for the
connection to the declared requirement and capability in their Bundle
Revisions. This technique makes it possible to depict fragments where a
capability in the fragment is actually available from the host's Bundle
Wiring.
BundleWiring
objects can continue to exist and
operate as long as there are wires from other BundleWiring
objects, even after a bundle is updated. The only way to break this
non-current wiring is to refresh the bundles that
are involved. The refresh operation computes a transitive closure of all
dependent bundles, and re-resolves them. Any active bundles in this
closure will be stopped and restarted. This operation can be activated
on the Framework Wiring.
The wiring API is based on the Bundle.adapt()
method,
see Adaptations. This method
allows the Bundle
object to be adapted to another type. For
example:
BundleWiring current = bundle.adapt(BundleWiring.class);
if ( current != null ) {
...
}
For this API, the following adaptations of the Bundle object are supported:
-
BundleRevision - Provides access to the current revision at the time of the
adapt
method call. A Bundle will always have a current Bundle Revision until it is uninstalled. -
BundleWiring - Provides access to the current Bundle Wiring at the time of the
adapt
method call. A current Bundle Wiring object only exists (the adapt method returns non-null
) when the bundle is resolved. -
BundleRevisions - Provides access to all the
BundleRevision
objects that are still in use. A Bundle always has aBundleRevisions
object, theadapt
method must never returnnull
. -
FrameworkWiring - Can only be adapted from the system bundle with bundle id 0. Provides access to the management methods like refresh and resolve, and information about bundles that are pending removal, and the dependency closure of a set of bundles.
The Bundle Wiring API is usable during launching after the
init
method has returned.
After an uninstall the adapt method will always return
null
for BundleRevision or BundleWiring. However, it is possible that the Bundle
Revision and/or its Bundle Wiring are reachable through other
bundles.
Packages are reflected in the osgi.wiring.package
namespace. An Import-Package clause is mapped to an
osgi.wiring.package
requirement and an Export-Package
clause is mapped into the corresponding capability. For example:
Import-Package: com.acme.foo;version=1
Export-Package: com.acme.foo;version=1
In the Requirements/Capabilities model this is depicted as in Figure 7.3.:
The following code prints the bundles that bundle A is wired to through Import-Package statements:
void printImports( Bundle A ) {
BundleWiring wA = A.adapt( BundleWiring.class );
for ( BundleWire wire :
wA.getRequiredWires(PACKAGE_NAMESPACE)) {
String pack = (String) wire.getCapability().getAttributes()
.get(PACKAGE_NAMESPACE);
Bundle bundle = wire.getProviderWiring()
.getBundle());
System.out.println(pack + " - " + bundle.getLocation());
}
}
Fragments use the osgi.wiring.host
namespace to
control their attachment. A fragment has a requirement for a host
capability, this is a capability with the bundle symbolic name and
version. If a fragment is attached then there is a wire from the
fragment's Bundle Wiring to the host's Bundle Wiring.
The following snippet finds the attached fragments of a bundle:
Set<BundleWiring> attachedFragments( BundleA ) {
Set<BundleWiring> result = new HashSet<BundleWiring>();
BundleWiring wA = A.adapt( BundleWiring.class );
for ( BundleWire wire : wA.getProvidedWires(HOST_NAMESPACE)) {
result.add( wire.getRequirerWiring() );
}
return result;
}
A bundle provides a simplified view of the state of the framework: it is either resolved or not. If it is resolved, bundles can become active and collaborate with other resolved bundles. During the time a bundle is resolved, and thus can see the environment, it will see a consistent stable state with respect to its code dependencies. Other bundles can be started and stopped, installed, updated, and uninstalled during the life cycle of a bundle. However, as long as a bundle is resolved it will continue to load classes from the bundle revisions it was wired to when it was initially resolved, even if those bundles are updated or uninstalled.
The consequence of this model is that each bundle can have multiple revisions, and each revision can have an optional wiring at any moment in time. Management agents have the need to see this more complex state to be able to predict the impact of management actions and to help diagnose problems.
There are two important event types that complicate the overall state. The install and update events provide a new Bundle Revision for a bundle and the uninstall event disconnects any Bundle Revisions from the bundle. The Bundle Revision contains the resources and the metadata defining, among other things, what type of bundle it is and what its dependencies are. An update can therefore change every aspect of a bundle. For example, an update could turn a non-fragment bundle into a fragment.
The other event types that is of interest here are the
RESOLVED
and UNRESOLVED
events. Resolving a
bundle creates a Bundle Wiring based on the then
current Bundle Revision. During resolving, a Bundle Wiring uses the
requirements from the Bundle Revision to create wires
to other Bundle Revisions; the wires are used to control the class loading
process. Once a Bundle Wiring is required by another Bundle Wiring, or it
is the current wiring, it is said to be in use. This
model is depicted in Figure 7.4.
The framework never eagerly disconnects the wires between Bundle
Wirings, a disconnect happens only under control of the management agent
when the refreshBundles(Collection,FrameworkListener...) method is called or when all requiring bundles
become uninstalled. When a bundle is updated, its existing
BundleWiring
objects will continue to serve classes and
resources to bundles that use it. The update, even though it provides a
new revision, has no effect on resolved bundles with respect to class
loading. Also, the installation of a new bundle could allow new wires but
they must not affect the existing wiring until refreshBundles(Collection,FrameworkListener...) is called (with the exception for dynamic
imports). Though the class loading wires remain in place, proper bundles
should react to the changes in the dynamic state. That is, when a bundle
is updated it will be stopped, allowing others to remove any references
they have to that bundle. However, even in those cases the wirings will
remain until the bundle is unresolved during a refresh cycle.
After an update, the existing Bundle Wiring is no longer current for the bundle.
Bundle Wirings that are not in use (no other Bundle Wiring is wired to it) can be removed immediately but in-use Bundle Wirings must remain in place until they become no longer in use. These non-current in-use Bundle Wirings are called pending for removal.
To forcefully remove all these non-current in use Bundle Wirings the framework can refresh a set of bundles at the request of a management agent. The refresh will create a transitive dependency graph on an initial set of bundles and then unresolves each bundle in the graph, which will make any of the stale Bundle Wirings no longer in use so they can be cleaned up. After this refresh, any previously active bundles will be restored to their previous state.
The purpose of this non-eager behavior is to allow for efficient handling of multiple updates/installs/uninstalls. Refreshing the wires after each such event requires the start and stop of the dependent bundles, disrupting the operations of the system unnecessary often. It is therefore better to batch up a number of such operations and then refresh the system once. However, the implication of this optimization is that the actual wiring between bundles can quickly become an intricate web of connections between revisions of bundles.
For example, assume a bundle A
is installed. At
installation, it will have a single Bundle Revision, called
A.0
. Next, bundle B
is installed, it will have a
Bundle Revision B.0
. Assuming Bundle Revision
A.0
requires a capability in bundle B
, resolving
bundle A
and bundle B
will create a Bundle
Wiring for Bundle Revision A.0
linking to a Bundle Wiring for
Bundle Revision B.0
. If bundle B
is now updated,
it will create a second Bundle Revision, B.1
. However, the
current Bundle Wiring for bundle A
(Bundle Revision
A.0
) will remain wired to Bundle Revision B.0
as
long as bundle A
and bundle B
remain resolved,
even though the current Bundle Revision for bundle B
has now
become B.1
. As long as Bundle Revision A.0
remains resolved, bundle B
's resolved state has no
impact.
Bundles are only actually unresolved when they are
refreshed, the UNRESOLVED
event only
indicates that a Bundle is updated or uninstalled. Refreshing happens on a
per bundle basis but takes any Bundle Wirings into account that depend on
the refreshed bundle. In the previous example, if bundle B
is
refreshed, it will automatically refresh bundle A
because
A
is wired to B
. That is, bundle B
is in use by A
. The refresh will stop bundles A
and B
and then unresolve both of them. Unresolving basically
means removing any reference from the framework to the Bundle Wirings of
the involved bundles. This unreferencing will allow the garbage collector
to remove any remains, like for example the class loader and the
activator, unless some bundles illegally hold on to references. Once a
Bundle Wiring is no longer required by the framework, it is set to be not
in use, regardless of stale references.
Normally, after unresolving, the bundles are started again in their
original state, forcing them to resolve again. In the previous example,
Bundle Revision A.0
will then be connected to the Bundle
Revision B.1
through newly created BundleWiring
objects. The old Bundle Wiring for B.0
will no longer be in
use and will thus be garbage collected.
This example is depicted in Figure 7.5. This picture
shows when the different objects are created and discarded. In this
picture bundle B
is not started.
The resolver is responsible for wiring Bundle Requirements and
Bundle Capabilities to each other while adhering to the semantics defined
in their namespace. For each paired Bundle Requirement and Bundle
Capability the resolver creates a Bundle Wire that links the Bundle
Requirement, the requiring Bundle Wiring, the providing Bundle Wiring, and
the Bundle Capability. The relationships between a bundle A
and bundle B
, where A
requires some capability
in B
, is depicted in Figure 7.6.
The OSGi framework can add wires and new requirements and capabilities after resolving during run time. This mechanism is for example used in DynamicImport-Package, dynamic attaching of fragments, and byte code weaving.
The type of a bundle is available on the Bundle Revision because a Bundle can change from a fragment to a normal bundle or vice versa after an update. The getTypes() method is used to obtain a bitmap that is either 0 or has the following bit set:
-
TYPE_FRAGMENT - If this bit is set the Bundle Revision is a fragment.
The type is a bitmap allowing future versions of this specification to add new types that can be a combination of existing and new types. The following example displays how a Bundle is checked to be a fragment:
BundleRevision rev = aBundle.adapt(BundleRevision.class);
if ( rev != null && (rev.getTypes() & TYPE_FRAGMENT)!= 0 ){
... // do the fragment thing
}
A fragment bundle will show all its declared capabilities and
requirements on its Bundle Revision but during resolving the resolver only
considers the osgi.wiring.host
and osgi.ee
requirements and the osgi.identity
capability and
requirements.
The osgi.wiring.host
requirement represents the
Fragment-Host header. A fragment can be attached to different hosts and
each attachment creates a wire from the fragment's Bundle Wiring to the
host's Bundle Wiring. The osgi.ee
requirement is also never
hosted.
The osgi.identity
capability of a fragment is part of
the fragment's Bundle Wiring and is not part of a host bundle's Bundle
Wiring. That is, each Bundle Wiring has exactly one
osgi.identity
capability. However, osgi.identity
requirements declared by a fragment are not part of the fragment's Bundle
Wiring and are instead hosted by the host bundle's Bundle Wiring.
Any other requirements and capabilities in a fragment bundle never become part of the fragment's Bundle Wiring; they are treated as part of the host's requirements and capabilities when the fragment is attached to that host.
To find the attached fragment for a host bundle it is necessary to
find the wires for the osgi.wiring.host
capability. The
requiring end of such a wire is the attached fragment and the providing
end is the attaching host.
For example, bundle A
is a host and bundle B is a
fragment as depicted in Figure 7.7 on page .
Then, to find the attached fragments for Bundle Revision
A0:
List<BundleWiring> attached = new ArrayList<BundleWiring>();
for ( BundleWire wire : A0.getBundleWiring().getProvidedWires(HOST_NAMESPACE))
attached.add( wire.getRequirerWiring() );
It is also possible to calculate the reverse dependency for finding
the hosts of a fragment. For the previous example, the bundles that attach
fragment B
can be found with:
List<BundleWiring> hosts = new ArrayList<BundleWiring>();
for ( BundleWire wire : B0.getBundleWiring().getRequiredWires(HOST_NAMESPACE))
hosts.add( wire.getProviderWiring() );
The osgi.wiring.host
namespace mandates that the
resolver moves the Bundle Requirements and Bundle Capabilities from the
fragment in all other namespaces than the osgi.wiring.host
,
osgi.identity
and osgi.ee
namespaces to the host
bundle. For example, if the fragment exports a package p
,
then this package is exported by the host. In such a case, the
BundleRequirement
and BundleCapability
objects
remain associated with the Bundle Revision of the fragment. However, the
Bundle Wire has the appropriate Bundle Wiring of the host. This is
depicted in Figure 7.8 on page . Package p
is
declared a capability in fragment B.0
but when wired the
Bundle Wiring of host A.0
will be the provider.
The previous example is also depicted as an instance diagram in Figure 7.9 on page .
There are a number of actions that are global in a framework and not
associated with a specific bundle. These actions are associated with the
framework; this is the reason for the Framework Wiring adaptation. The
system bundle (bundle 0) can be adapted to a FrameworkWiring
object:
FrameworkWiring fw = systemBundle.adapt(FrameworkWiring.class);
The Framework Wiring provides the following actions:
-
findProviders(Requirement) - The find providers method returns capabilities available in the framework that match the given requirement. This method can be used to search for capabilities provided by bundles in the framework. For example, an exported package with a specific package name.
-
getDependencyClosure(Collection) - The dependency closure method takes a seed of bundles for a dependency closure and then add any bundles that depend a bundle in the dependency closure, recursively. The result can be used to calculate the impact of a refresh operation. If the framework is refreshed the result of this method provides the bundles that will be affected.
-
getRemovalPendingBundles() - Bundles that have a Bundle Wiring that is in use but not current. Such bundles are pending removal.
-
refreshBundles(Collection,FrameworkListener...) - See Refreshing.
-
resolveBundles(Collection) - Attempt to resolve all the bundles in the given collection. This action can also cause bundles to become resolved outside the given collections.
The update of bundles will create new Bundle Revisions while the existing Bundle Wirings remain wired to their previous Bundle Revisions. This stale wiring must be cleaned up and the refreshBundles(Collection,FrameworkListener...) method achieves this.
The refreshBundles
method works from an initial
collection of bundles that is used to seed the calculation of the
dependency closure. The dependency closure is
calculated by expanding the seed dependency closure to include any
bundle that has a Bundle Wiring that depends on any bundle in the
dependency closure. This is a recursive definition so the dependency
closure contains the list of transitive dependencies on the initial seed
collection.
This dependency closure can be obtained separately with the getDependencyClosure(Collection) method providing it with the same seed. If no
seed is provided, that is a null
argument is given, the
refreshBundles
method will be identical to calling it with
the result of the getRemovalPendingBundles() method as the seed collection. This default will
ensure that all stale Bundle Wirings will be cleaned up.
The refresh process will stop any bundles in the considered
collection while recording their state and, if active, their starting
option (for example START_TRANSIENT
). Stopping must take
any start level rules into account.
The refresh must then unresolve all the bundles in the considered collection. Unresolving will cause all the removal pending Bundle Wirings to become no longer in use because there are no longer any bundles requiring them. This will make the Bundle Wirings available for garbage collection because they are then no longer reachable from the framework API.
The framework must then attempt to restore the state as it was before the refresh taking all the framework rules into account, including start levels, start options, and activation options.
The actual refresh operation will take place in the background
because it can be a long running operation. The refresh operation will
send out a global framework event PACKAGES_REFRESHED
.
However, catching this event properly is non-trivial. For this reason,
the refreshBundle
method also allows a callback by
specifying an optional Framework Listener in the method invocation that
will only be called when the method is finished. For example:
fw.refreshBundles( null, new FrameworkListener(){
public void frameworkEvent(FrameworkEvent ev) {
System.out.println("Refresh finished");
}
});
A resolved bundle can consist of a number of containers: the basic bundle container (usually a JAR), embedded JARs or directories, and fragments. Containers contain entries but the Bundle-ClassPath header turns these entries into a single namespace, called resources. These concepts are fully defined in Bundle Class Path.
The wiring API provides two different ways to iterate over the contents in the containers that constitute a resolved bundle:
-
Bundle Class Path Order - Scan the bundle class path containers.
-
Entry Order - Scan all the entries that constitute a bundle and its attached fragments.
These two different ways are outlined in the following sections.
Once a bundle is resolved all its container namespaces are flattened to a single namespace that is then used by the class loader. Flattening has as a consequence that certain resources will disappear from the view, which resource remains and which disappear depends on the order of the flattening. The OSGi specification defines exactly what this order is. However, the rules for this ordering are many and non-trivial. For this reason, a Bundle Wiring allows the iteration over the resources of a bundle in the bundle class path order, reflecting the same flattening as that what the class loader will do. A bundle must be resolved to be able to iterate over its resources.
The method used to iterate over the resources in bundle class path
order is listResources(String,String,int). This method takes a starting path in the
namespace, a pattern to match (for example *.class
for
class resources) and a flag to indicate if the scan should recurs into
directories or not.
When the bundle class path has a multi-release container, see Multi-release Container, and an argument to the listResources(String,String,int) method would include a resource name in the method result if the resource was not available from the root directory but is available from a versioned directory visible on the current Java version, then the method result must include the resource name from the root directory. For example, if the multi-release container has the following entry
META-INF/versions/9/com/foo/resource.txt
and the call listResources(“/com/foo”, “*.txt”, 0)
is
made when running on Java 9, or later, the result must include
com/foo/resource.txt
The listResources(String,String,int) method has no counterpart in the standard class loader API.
A Bundle Wiring reflects a resolved bundle. This wiring
constitutes the bundle and any attached fragments. The findEntries(String,String,int) method is similar to the
Bundle.
findEntries(String,String,boolean) method. The Bundle's method will be identical
when the bundle can be resolved, if the bundle cannot resolve the
Bundle's findEntries
method has a fallback that allows
iteration without attached fragments. The Bundle Wiring's findEntries(String,String,int) is always against a resolved bundle because it
is on a Bundle Wiring.
The class loader can also be obtained from the
BundleWiring
class with the getClassLoader() method.
The Bundle Wiring API requires Adapt Permission with action
ADAPT
for the following types:
-
org.osgi.framework.wiring.BundleWiring
-
org.osgi.framework.wiring.BundleRevision
-
org.osgi.framework.wiring.BundleRevisions
-
org.osgi.framework.wiring.FrameworkWiring
The Framework Wiring methods that mutate state require an additional Admin Permission with the action:
-
RESOLVE
(for the system bundle) - For refreshBundles(Collection,FrameworkListener...) and resolveBundles(Collection)