56 Weaving Hook Service Specification

56.1 Introduction

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.

56.1.1 Essentials

  • 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.

56.1.2 Entities

  • Weaving Hook - A service registered by a bundle that wants to weave other bundles.

  • Woven Class - An object representing the class to be woven.

  • Woven Class Listener - A service registered by a bundle that wants to observe woven class state transitions.

Figure 56.1 Byte Code Weaving

Byte Code Weaving

56.2 Usage

56.2.1 Tracing

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) {
        ..
    }       
}

56.2.2 Isolation

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) {
        ...
    }
}

56.3 Weaving Hook

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:

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.

56.3.1 Concurrency

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.

56.3.2 Error Handling

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:

  1. 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.

  2. 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.

  3. The WovenClass object must be marked as complete. All remaining Weaving Hook services must be skipped.

  4. The bundle class loader must throw a ClassFormatError with the cause being the exception thrown by the Weaving Hook service.

56.4 Woven Class Listener

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.

Figure 56.2 Woven Class State Diagram

Woven Class State Diagram

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

TRANSFORMING

A bundle class load request was made.

  • Weaving is incomplete.

  • The class is undefined.

  • The Woven Class is mutable.

  • Weaving Hooks are notified but Woven Class Listeners are not.

TRANSFORMED

All Weaving Hooks have been notified.

  • Weaving is complete.

  • The class is undefined.

  • The Woven Class is effectively immutable.

  • Woven Class Listeners are notified.

TRANSFORMING_FAILED

A Weaving Hook threw an exception.

  • Weaving is incomplete.

  • The class is undefined.

  • The Woven Class is effectively immutable.

  • Woven Class Listeners are notified.

DEFINED

All Woven Class Listeners have been notified. The class has been defined.

  • Weaving is complete.

  • The class is defined.

  • The Woven Class is effectively immutable.

  • Woven Class Listeners are notified.

DEFINE_FAILED

All Weaving Hooks have been notified. A class definition failure occurred.

  • Weaving is complete.

  • The class is undefined.

  • The Woven Class is effectively immutable.

  • Woven Class Listeners are notified.


56.4.1 Concurrency

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.

56.4.2 Error Handling

Woven Class Listeners must not cause the weaving process to fail. If a Woven Class Listener throws an exception, the framework should log the exception but otherwise ignore it.

56.5 Security

56.5.1 Weaving Hooks

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.

56.5.2 Woven Bundles

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.

56.5.3 Woven Class Listeners

Implementers of these listeners must have:

  • ServicePermission[..WovenClassListener,REGISTER] for Woven Class Listener services.

56.6 org.osgi.framework.hooks.weaving

Version 1.1

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)"

56.6.1 Summary

56.6.2 public class WeavingException
extends RuntimeException

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.

56.6.2.1 public WeavingException(String msg, Throwable cause)

The associated message.

The cause of this exception.

Creates a WeavingException with the specified message and exception cause.

56.6.2.2 public WeavingException(String msg)

The message.

Creates a WeavingException with the specified message.

56.6.3 public interface WeavingHook

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

56.6.3.1 public void weave(WovenClass wovenClass)

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

56.6.4 public interface WovenClass

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

56.6.4.1 public static final int DEFINE_FAILED = 16

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

56.6.4.2 public static final int DEFINED = 4

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

56.6.4.3 public static final int TRANSFORMED = 2

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

56.6.4.4 public static final int TRANSFORMING = 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

56.6.4.5 public static final int TRANSFORMING_FAILED = 8

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

56.6.4.6 public BundleWiring getBundleWiring()

Returns the bundle wiring whose class loader will define the woven class.

The bundle wiring whose class loader will define the woven class.

56.6.4.7 public byte[] getBytes()

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.

56.6.4.8 public String getClassName()

Returns the fully qualified name of the class being woven.

The fully qualified name of the class being woven.

56.6.4.9 public Class<?> getDefinedClass()

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.

56.6.4.10 public List<String> getDynamicImports()

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.

56.6.4.11 public ProtectionDomain getProtectionDomain()

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.

56.6.4.12 public int getState()

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

56.6.4.13 public boolean isWeavingComplete()

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.

56.6.4.14 public void setBytes(byte[] newBytes)

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.

56.6.5 public interface WovenClassListener

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

56.6.5.1 public void modified(WovenClass wovenClass)

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.

56.7 References