Hello,

Michael Warres identified a number of issues with class loading and codebase annotation loss, none of which were fully addressed. https://dl.acm.org/doi/pdf/10.5555/1698139

I wanted to share with you an new JERI InvocationLayerFactory I've been working on, on and off for some time.

Existing implementations that marshal objects between two endpoints, associate a codebase annotation with every class in the stream.

I have done something much different.

First I don't send any codebase annotations in the stream.  I recognised that we shouldn't have a second class resolution mechanism, that only ClassLoaders should control class resolution and that each node may not only have different ClassLoader trees or maps, but have different ways of assigning and wiriing them up.

Instead I decided to make the codebase annotation a Configuration concern of the service.

Both endpoints of the service are wired up with ClassLoaders, the ServerEndpoint ClassLoader is defined in the Configuration by specifying a class (typically the services proxy implementation class) whose ClassLoader will be used for class resolution at the ServerEndpoint.

The client Endpoint is assigned a ClassLoader using a Service Provider interface  ProxyCodebaseSpi, which can be used to support modular environments.   The default service provider implementation assigns a PreferredClassLoader.   The codebase annotation is configured by the service implementation, the codebase annotation is obtained at the client using a bootstrap proxy that implements an interface called CodebaseAnnotation.

These marshalling streams are only responsible for resolving classes related to one service, if another service proxy is passed as a parameter to a service, it will be marshalled and resolved with it's own independent endpoints and client ClassLoader assigned specifically for that service.

Services now have independent class resolution determined by the ClassLoader's at their Endpoint's.

All services must implement the ProxyAccessor interface, whenever the stream detects an instance of ProxyAccessor, it obtains the service proxy from the service, then marshalls it into a MarshalledInstance.  The stream also asks the service for a bootstrap proxy which implements CodebaseAccessor ( classes for which are always resolvable by the stream), then the CodebaseAccessor proxy is sent with the MarshalledInstance , over the marshalling stream, so as to avoid polluting the stream with classes from other services it cannot resolve.

When the MarshalledInstance is deserialized/unmarshalled by the client endpoint, the CodebaseAccessor is used to obtain the codebase annotation, a ClassLoader is created by the provider and the service proxy is unmarshalled using the ClassLoader provided.   The CodebaseAccessor proxy's InvocationHandler (which contains the ObjectEndpoint) is used along with the codebase annotation to represent the service's Identity.   This allows service proxy's to be assigned a different ClassLoader to other services who share identical annotations, this is important as the ClassLoader represents the ServerEndpoint's Subject and whatever permissions it's granted at the cleint.

Note that constraints are applied and authentication occurs prior to unmarshalling the proxy.

Modular environments can choose how to create this ClassLoader and how to resolve it, using the CodebaseAccessor (which can also provide code signer certificates, the codebase annotation and the identity of the service).

The default provider implementation PreferredProxyCodebaseProvider utilises PreferredClassLoader and typically uses the client's ClassLoader as the parent ClassLoader for the service proxy ClassLoader, so the service proxy has the same class visibility as the client (with the exception of preferred classes).   The default provider is not perfect for modular environments like Maven and OSGi, since it is a compromise that must function in all environments, but it is an acceptable compromise.

1. Firstly if a service proxy returns to a node from which it was
   exported, it will always be loaded by the ClassLoader from which it
   was exported, in this case it is the responsibility of the local
   environment to ensure visibility.
2. Secondly if a service proxy is unmarshalled by itself, it will not
   create a new ClassLoader, it must confirm the annotation doesn't
   match the stream loader.
3. Thirdly, if a proxy has been unmarshalled previously, has the same
   InvocationHandler, annotations and parent loader as the stream, it
   will be loaded by the cached ClassLoader.
4. A new ClassLoader will be created with the stream ClassLoader as
   it's parent loader (the client that's unmarshalled it).

This provides a reasonable implementation that works in all cases, in OSGi for example, different bundles may download the same service proxy, each will have a separate copy that resolves to the classes visible to each client's bundle as the client bundle ClassLoader will be the parent of the service proxy ClassLoader.   In this case service codebase annotations would include the proxy and the service API jar URL's, in case some interfaces were not imported by the client's OSGi bundle (not resolvable by local code).

Code that is resolved locally and reserialized across the marshalling stream will not lose it's identity as it is resolved by the ClassLoader at the remote Endpoint, therefore codebase annotation loss issues don't occur.

The challenge for modular environments is creating a ClassLoader that is unique to the service while also wiring dependencies for maximum compatibility.

Once the ClassLoader has been assigned to a client Endpoint it will continue to be used without consulting codebase annotations again.

With this model, there are no codebase annotations to lose and the server is always consulted for the configured codebase annotation whenever a service proxy is serialized and the use of ClassLoader's is compatible with Modular environments like OSGi and Maven / Plexus Classworlds.

This also allows for evolution of serialized proxy state, to a new later versioned codebase for example, rather than transferring the old codebase annotation which was subject to change from codebase annotation loss from node to node.

I think this has considerably simplified class resolution for services by handing back responsibility to ClassLoaders.

Note that it's complimentary to existing infrastructure, without requiring anyone to change if they don't want to.

This will also eliminate the many problems that codebase annotation loss causes for new developers.

Regards,

Peter Firmstone.

Reply via email to