Bytecode weaving is a popular technique that transforms class files to provide additional functionality. This is a powerful technique that, when used with care, can significantly reduce the coding effort for mundane programming chores.
This specification provides a means for a handler bundle to intercept any bundle class loading calls in the framework, transform the byte codes, and add new dynamic imports. A means for observing the final weaving results, both before and after they take effect, is also provided.
-
Ordering - Allow a weaver to weave a bundle before it is loaded and used.
-
Dynamic Imports - Support additional imports.
-
Strategy - Support both Static and Dynamic Weaving strategies.
-
No Metadata - Allow standard bundles, without any specific metadata, to be woven.
-
Java API - Use the existing Java byte code transformation APIs where possible.
-
Multiple - Allow for multiple weavers per bundle in a defined order.
-
Observation - Allow woven class state transitions to be observed.
For tracing purposes, a bundle can weave a trace entry and exit message around each method. This can be done with byte code weaving by inserting a call to a service at the beginning and end of a method. In this example, a service is created that has two methods:
-
trace(Bundle)
- Byte code weave the given bundle with trace entry and exit methods. -
untrace(Bundle)
- Remove any weavings.
The strategy chosen here is simple. The weaver registers a Weaving Hook service so it receives all class loads. Any such class load is checked against a set of bundles that needs tracing, any class from a traced bundle is then woven with the trace information. If a bundle is to be traced, the set is updated and the bundle is refreshed to ensure all classes are loaded anew so the tracing code can be woven in.
public class TracingWeaver implements WeavingHook {
final Set<Bundle> bundles = new HashSet<Bundle>();
final List<String> imports = Arrays.asList(
"com.acme.trace;version=\"[1,2)\"");
BundleContext context;
The weave
method is the service method of the Weaving
Hook service. It receives a WovenClass
object that provides
the information about the class to be woven. In this case, the
bundles
field contains the bundles to be woven, so this is
checked first. If the to-be-loaded class is in one of the traced
bundles, the byte codes are changed to provide trace information.
Otherwise the no change is made.
In general, weaving code will require new imports for the
to-be-woven bundle. These imports can be added by adding Dynamic Import
Package clauses to the list received from the getDynamicImports() method. This is a list of String
objects, the syntax of each string is equal to a clause in the
DynamicImport-Package header. See Dynamic Import Package for the proper
syntax.
public void weave(WovenClass classInfo) {
BundleWiring bw = classInfo.getBundleWiring();
Bundle b = bw.getBundle();
if (bundles.contains(b)) {
byte [] woven = weaveTracing(classInfo.getBytes());
if (!classInfo.getDynamicImports().containsAll(imports))
classInfo.getDynamicImports().addAll(imports);
classInfo.setBytes(woven);
}
}
The following trace
method is called when a bundle
must be traced. It ignores the request if the bundle is already traced.
Otherwise, it will add the bundle to the set of traced bundles and
refresh the bundle.
public void trace(Bundle b) {
if (bundles.add(b))
refresh(b);
}
The untrace
method is the reverse:
public void untrace(Bundle b) {
if (bundles.remove(b))
refresh(b);
}
The refresh method uses the Bundle Wiring API to refresh a bundle. Refreshing a bundle will throw away its class loader so that all used classes are reloaded when needed.
private void refresh(Bundle b) {
Bundle fwb = context.getBundle(0);
FrameworkWiring fw = fwb.adapt(FrameworkWiring.class);
fw.refreshBundles(Arrays.asList(b));
}
The trace method that does the final weaving is left as an exercise to the reader:
byte[] weaveTracing(byte[] bytes) {
..
}
}
The Resolver Hook Service Specification allows bundles to be separated into various regions isolated by sharing policies. The dynamic imports added in the tracing example will need to be taken into account by the sharing policies of regions containing bundles whose classes were woven in order for the bundles to resolve. This can be accomplished using a Woven Class Listener. Using a Weaving Hook would not be appropriate since there is no guarantee that a Weaving Hook observes the final list of dynamic imports.
The region registers a Woven Class Listener service so it receives notifications of Woven Class state transitions. The sharing policy of the region containing the bundle whose class was woven is updated with the dynamic imports, if any. This action occurs while the Woven Class is in the TRANSFORMED state to ensure the region is prepared to accept the imports before the bundle wiring is updated upon entering the DEFINED state. The region is initialized with the set of bundles composing it and a static sharing policy consisting of namespaces mapped to sets of filters indicating the allowed capabilities.
public class Region implements WovenClassListener, ResolverHook {
final Set<Bundle> bundles;
final Map<String, Set<Filter>> policy;
The modified
method is the service method of the
Woven Class Listener service. It receives a Woven Class object that
provides the information about the woven class that underwent a state
transition. If the current state is TRANSFORMED, and the associated bundle is part of the
region, the sharing policy is updated with the additional dynamic
imports, if any.
public void modified(WovenClass wovenClass) {
if ((wovenClass.getState() & WovenClass.TRANSFORMED) == 0)
return;
Bundle bundle = wovenClass.getBundleWiring().getBundle();
if (!bundles.contains(bundle))
return;
Set<Filter> filters = policy.get(PackageNamespace.PACKAGE_NAMESPACE);
for (String dynamicImport : wovenClass.getDynamicImports())
filters.add(toFilter(dynamicImport));
}
The region also implements ResolverHook
. When the
filterMatches
method is called, the requirement is
inspected to see if its matching capabilities are allowed into the
region. If not, the list of candidates is cleared.
public void filterMatches(BundleRequirement requirement,
Collection<BundleCapability> candidates) {
Bundle bundle = requirement.getRevision().getBundle();
if (!bundles.contains(bundle))
return;
String namespace = requirement.getNamespace();
if (!policy.containsKey(namespace))
return;
Map<String, String> directives = requirement.getDirectives();
String filter = directives.get(
PackageNamespace.REQUIREMENT_FILTER_DIRECTIVE);
Set<Filter> filters = policy.get(namespace);
if (!filters.contains(toFilter(filter)))
candidates.clear();
}
The toFilter
method responsible for converting the
requirement filters and dynamic import package clauses into a Filter is
left as an exercise to the reader.
private Filter toFilter(String s) {
...
}
}
The Weaving Hook service is a [1] Whiteboard Pattern service. Any party that wants to participate in weaving classes can register such a service. The framework obtains the Weaving Hook services and calls their weave(WovenClass) method for each class that must be loaded. The Weaving Hook services must be called in the service ranking order. See Service Ranking Order.
The Weaving Hook weave
method is called with a WovenClass object that represents the class to be woven. This
class is similar to the Java ClassFileTransformer
class but
adds bundle wiring information and can be made available in environments
prior to Java 5. The methods must all be called in privileged blocks. See
Privileged Callbacks.
The WovenClass
object provides access to:
-
getClassName() - The name of the class being loaded,
-
getBundleWiring() - The bundle wiring, which provides access to the bundle, the bundle class loaders and the capabilities.
-
getProtectionDomain() - The protection domain it is being defined in, and
-
getBytes() - The class bytes to be defined.
A Weaving Hook service can use the WovenClass
object to
decide to weave or not to weave. This decision can be based on the bundle
wiring, the class name, the protection domain, or the bytes. For example,
the following code checks if the class comes from a specific
bundle:
if (wovenClass.getBundleWiring().getBundle().equals(mybundle))
...
If the Weaving Hook service decides to weave the class, it must calculate new bytes for the provided Woven Class, these bytes can be set with the setBytes(byte[]) method. This implies that ordering is relevant. Weaving Hook services that are lower in ranking (called later) will weave any of the changes of higher ranking Weaving Hook services. Not all combinations of Weaving Hook services will therefore work as expected.
Weaving a class can create new dependencies that are unknown to the
woven class. In the trace example, the entry and exit traces require
access to the tracing subsystem, a dependency the original class did not
have. The WovenClass
object allows these extra imports to be
added as new dynamic import package clauses. The current set of dynamic
imports for the Woven Class is available from the WovenClass
getDynamicImports() method. This method returns a mutable list of
String
during the weave
method, the Weaving Hook
service can add new dynamic import package clauses to this list while the
weave
method is active. The syntax of the strings is defined
by the DynamicImport-Package header, see Dynamic Import Package. The dynamic imports
must have a valid syntax, otherwise an Illegal Argument Exception must be
thrown. These dynamically added dependencies are made visible through the
Bundle Wiring API Specification as new requirements. The
getRevision
method of these requirements must return the
Bundle Revision of the bundle that is woven; that revision will not
include these synthetic requirements in the
getDeclaredRequirements
method.
Dynamic imports are the last resort for the framework to find a provider when the normal imports fail. The woven class dynamic imports are consulted after the dynamic imports specified in the bundle's manifest. Frameworks must append all additional dynamic imports in the given order but are expected to filter out duplicates for performance reasons.
The management of the dynamic import is error prone and should be handled with care because dynamic imports use wildcards. Wildcards make the ordering of the imports important. In the pathological case, a full wildcard in the beginning (for example in the manifest) will void any more specific clauses that were added by Handlers. Handlers should be as specific as possible when adding dynamic import clauses.
In many cases the additional imports must refer to classes that the Handler is already resolved to. In an OSGi framework, the same package can be present multiple times. A Handler should therefore ensure that the woven bundle gets wired to the correct bundle. One way to achieve this is to include the bundle-version and bundle-symbolic-name synthetic attributes that are available on every exported package.
com.acme.weavesupport.core;version=1.2;bundle-version=3.2; «
bundle-symbolic-name=com.acme.weavesupport
After calling the last Weaving Hook service, the
WovenClass
object is made complete. The
framework must make the WovenClass
object immutable when it
is complete. After the Woven Class is complete, the current bytes are then
used to define the new class. Attempts to modify it, or any of its
properties, must throw an Exception. After completion, the getBytes() method must return a copy of the byte array that
was used to define the class.
Class loads can occur at any time and Weaving Hook services must be able to handle concurrent as well as re-entrant calls to the weave method. The framework should not hold any locks when calling the Weaving Hook services, and Weaving Hook service implementations must be thread-safe. Furthermore Weaving Hook services may be re-entrant, and should be careful to avoid cycles when weaving.
For example when a class is being woven, the Weaving Hook may implicitly load a class by having a reference to it or the Weaving Hook can explicitly load a class. This new class load will also pass through the Weaving Hook service, so care must be taken to avoid infinite looping.
Weaving hooks are very low level and care must be taken by the Weaving Hook services to not disrupt normal class loading. In the case that a weaving hook throws an unexpected exception the framework must do the following:
-
If the exception is not a Weaving Exception:
-
The framework must blacklist the weaving hook registration and never call that Weaving Hook service again as long as it is registered. This Weaving Hook service is considered blacklisted.
-
-
A framework event of type
ERROR
should be published that must include the Exception thrown by the Weaving Hook service. The source must be the bundle registering the Weaving Hook service. -
The
WovenClass
object must be marked as complete. All remaining Weaving Hook services must be skipped. -
The bundle class loader must throw a
ClassFormatError
with the cause being the exception thrown by the Weaving Hook service.
The Woven Class Listener service is a [1] Whiteboard Pattern service. Any party that wants to receive notifications of woven class state transitions can register such a service. The framework obtains the Woven Class Listener services and calls their modified(WovenClass) method whenever a Woven Class undergoes a state transition. The framework must not obtain Woven Class Listener services if there are no Weaving Hook services registered. In this case, if the party needs to receive notifications of woven class state transitions then a no-op Weaving Hook service implementation can be registered to ensure Woven Class Listener services are called.
The Woven Class Listener modified
method is called with
a WovenClass object that represents the woven class that
underwent a state transition. The method must be called in a privileged
block. See Privileged Callbacks.
The following diagram depicts the state transitions of a Woven Class.
Woven Class Listeners are not notified of the TRANSFORMING state because the Woven Class is mutable and listeners are not permitted to mutate the Woven Class. For all states observed by Woven Class Listeners, the Woven Class is effectively immutable. The first notification received for a given Woven Class is either the TRANSFORMED or TRANSFORMING_FAILED state. The TRANSFORMED state occurs after all Weaving Hooks have been notified but before the class has been defined or the bundle wiring has been updated for any additional dynamic imports. The TRANSFORMING_FAILED state occurs if any Weaving Hook throws an exception. After the TRANSFORMED state, a Woven Class can transition to either the DEFINED state or the DEFINE_FAILED state. The DEFINED state occurs when the class was defined successfully and after the bundle wiring has been updated. The DEFINE_FAILED state occurs if a class definition error occurred.
Table 56.1 describes the states of a Woven Class in more detail.
Table 56.1 Woven Class State Table
State | Description |
---|---|
A bundle class load request was made.
|
|
All Weaving Hooks have been notified.
|
|
A Weaving Hook threw an exception.
|
|
All Woven Class Listeners have been notified. The class has been defined.
|
|
All Weaving Hooks have been notified. A class definition failure occurred.
|
Class loads can occur at any time, and Woven Class Listeners must
be able to handle concurrent calls to the modified
method.
The framework should not hold any locks when calling Woven Class
Listeners, and Woven Class Listener implementations must be thread-safe.
Woven Class Listeners must be synchronously called by the framework when
a Woven Class completes a state transition. The woven class processing
will not proceed until all Woven Class Listeners are done.
All hooks described in this specification are highly complex facilities that require 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 hook services is the framework. Therefore, there is never a need to provide:
-
ServicePermission[..WeavingHook,GET]
Implementers of these hooks must have:
-
ServicePermission[..WeavingHook,REGISTER]
for Weaving Hook services.
In addition, a Weaving Hook must have Admin Permission with the
WEAVE action to be able to use the methods on the
WovenClass
object that mutate the state like setBytes(byte[]), getBytes(), and the mutating methods on the list returned
by getDynamicImports(). Moreover, a Weaving Hook must have Package
Permission with the IMPORT
action in order to add or
replace dynamic imports.
The Framework must grant implied PackagePermission[somePkg,
IMPORT]
permissions to bundles whose classes are being woven with
additional dynamic imports, assuming the weaver has a matching package
import permission. The permission check for the weaver must occur during
any call to the list that results in the addition or setting of a
dynamic import. If the check fails, a SecurityException must be thrown.
If it succeeds, the implied permission must be granted to the woven
bundle immediately before defining the class.
Framework Weaving Hooks Package Version 1.1.
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.weaving; version="[1.1,2.0)"
-
WeavingException
- A weaving exception used to indicate that the class load should be failed but the weaving hook must not be blacklisted by the framework. -
WeavingHook
- OSGi Framework Weaving Hook Service. -
WovenClass
- A class being woven. -
WovenClassListener
- Woven Class Listener Service.
A weaving exception used to indicate that the class load should be failed but the weaving hook must not be blacklisted by the framework.
This exception conforms to the general purpose exception chaining mechanism.
The associated message.
The cause of this exception.
Creates a WeavingException
with the specified message and
exception cause.
OSGi Framework Weaving Hook Service.
Bundles registering this service will be called during framework class loading operations. Weaving hook services are called when a class is being loaded by the framework and have an opportunity to transform the class file bytes that represents the class being loaded. Weaving hooks may also ask the framework to wire in additional dynamic imports to the bundle.
When a class is being loaded, the framework will create a WovenClass object for the class and pass it to each registered weaving hook service for possible modification. The first weaving hook called will see the original class file bytes. Subsequently called weaving hooks will see the class file bytes as modified by previously called weaving hooks.
Thread-safe
The WovenClass object that represents the data that will be used to define the class.
Weaving hook method. This method can modify the specified woven class object to weave the class being defined.
If this method throws any exception, the framework must log the exception and fail the class load in progress. This weaving hook service must be blacklisted by the framework and must not be called again. The blacklisting of this weaving hook service must expire when this weaving hook service is unregistered. However, this method can throw a WeavingException to deliberately fail the class load in progress without being blacklisted by the framework.
WeavingException
– If this weaving hook wants to deliberately fail
the class load in progress without being blacklisted by the
framework
A class being woven. This object represents a class being woven and is passed to each WeavingHook for possible modification. It allows access to the most recently transformed class file bytes and to any additional packages that should be added to the bundle as dynamic imports.
Upon entering one of the terminal states, this object becomes effectively immutable.
Not Thread-safe
Consumers of this API must not implement this type
The woven class failed to define.
The woven class is in this state when a failure occurs while defining the class. The woven class cannot be further transformed or defined. This is a terminal state. Upon entering this state, this object is effectively immutable.
1.1
The woven class has been defined.
The woven class is in this state after the class is defined. The woven class cannot be further transformed. This is a terminal state. Upon entering this state, this object is effectively immutable, the bundle wiring has been updated with the dynamic import requirements and the class has been defined.
1.1
The woven class has been transformed.
The woven class is in this state after weaving hooks have been called and before the class is defined. The woven class cannot be further transformed. The woven class is in this state while defining the class. If a failure occurs while defining the class, the state transitions to DEFINE_FAILED. Otherwise, after the class has been defined, the state transitions to DEFINED.
1.1
The woven class is being transformed.
The woven class is in this state while weaving hooks are being called. The woven class is mutable so the class bytes may be modified and dynamic imports may be added. If a weaving hook throws an exception the state transitions to TRANSFORMING_FAILED. Otherwise, after the last weaving hook has been successfully called, the state transitions to TRANSFORMED.
1.1
The woven class failed to transform.
The woven class is in this state if a weaving hook threw an exception. The woven class cannot be further transformed or defined. This is a terminal state. Upon entering this state, this object is effectively immutable.
1.1
Returns the bundle wiring whose class loader will define the woven class.
The bundle wiring whose class loader will define the woven class.
Returns the class file bytes to be used to define the named class.
While in the TRANSFORMING state, this method returns a reference to the class files byte array contained in this object. After leaving the TRANSFORMING state, this woven class can no longer be transformed and a copy of the class file byte array is returned.
The bytes to be used to define the named class.
SecurityException
– If the caller does not have
AdminPermission[bundle,WEAVE]
and the Java runtime
environment supports permissions.
Returns the fully qualified name of the class being woven.
The fully qualified name of the class being woven.
Returns the class defined by this woven class. During weaving, this
method will return null
. Once weaving is
complete, this method will return the class
object if this woven class was used to define the class.
The class associated with this woven class, or null
if
weaving is not complete, the class definition failed or this
woven class was not used to define the class.
Returns the list of dynamic import package descriptions to add to the bundle wiring for this woven class. Changes made to the returned list will be visible to later weaving hooks called with this object. The returned list must not be modified outside invocations of the weave method by the framework.
After leaving the TRANSFORMING state, this woven class can no longer be transformed and the returned list will be unmodifiable.
If the Java runtime environment supports permissions, any modification to
the returned list requires AdminPermission[bundle,WEAVE]
.
Additionally, any add or set modification requires
PackagePermission[package,IMPORT]
.
A list containing zero or more dynamic import package
descriptions to add to the bundle wiring for this woven class.
This list must throw IllegalArgumentException
if a
malformed dynamic import package description is added.
Core Specification, Dynamic Import Package, for the syntax of a dynamic import package description.
Returns the protection domain to which the woven class will be assigned when it is defined.
The protection domain to which the woven class will be assigned
when it is defined, or null
if no protection domain will
be assigned.
Returns the current state of this woven class.
A woven class can be in only one state at any time.
Either TRANSFORMING, TRANSFORMED, DEFINED, TRANSFORMING_FAILED or DEFINE_FAILED.
1.1
Returns whether weaving is complete in this woven class. Weaving is complete after the class is defined.
true
if state is DEFINED,
TRANSFORMING_FAILED or DEFINE_FAILED;
false
otherwise.
The new classfile that will be used to define the named class. The specified array is retained by this object and the caller must not modify the specified array.
Set the class file bytes to be used to define the named class. This method must not be called outside invocations of the weave method by the framework.
While in the TRANSFORMING state, this method replaces the reference to the array contained in this object with the specified array. After leaving the TRANSFORMING state, this woven class can no longer be transformed and this method will throw an IllegalStateException.
NullPointerException
– If newBytes is null
.
IllegalStateException
– If state is TRANSFORMED,
DEFINED, TRANSFORMING_FAILED or
DEFINE_FAILED.
SecurityException
– If the caller does not have
AdminPermission[bundle,WEAVE]
and the Java runtime
environment supports permissions.
Woven Class Listener Service.
Bundles registering this service will receive notifications whenever a woven class completes a state transition. Woven Class Listeners are not able to modify the woven class in contrast with weaving hooks.
Receiving a woven class in the TRANSFORMED state allows listeners to observe the modified byte codes before the class has been DEFINED as well as the additional dynamic imports before the bundle wiring has been updated.
Woven class listeners are synchronously called when a woven class completes a state transition. The woven class processing will not proceed until all woven class listeners are done.
If the Java runtime environment supports permissions, the caller must have
ServicePermission[WovenClassListener,REGISTER]
in order to register a
listener.
1.1
Thread-safe
The woven class that completed a state transition.
Receives notification that a woven class has completed a state transition.
The listener will be notified when a woven class has entered the TRANSFORMED, DEFINED, TRANSFORMING_FAILED and DEFINE_FAILED states.
If this method throws any exception, the Framework must log the exception but otherwise ignore it.
[1]Whiteboard Patternhttps://docs.osgi.org/whitepaper/whiteboard-pattern/