I absolutely agree with the requirements you state.

The problem with Jini (and hence River) usage of TCCL is that it assumes a parent-child relationship between class loaders - which in turn causes the issues with transferring object graphs I've described earlier.

What I understood when working on this is also that OSGI is not the right choice either :) What I also understood is that even having a "module" concept in Java is not enough :)

Any solution needs to make a distinction between:
- a static notion of a module (in OSGI it is a bundle, in current Jini - a module represented by a codebase string) - a dynamic notion of a module (in OSGI its a BundleWiring, in current Jini it would be the _hierarchy_ of class loaders) - it is a runtime state representing a module with its resolved dependencies And the most important conclusion is that it is the latter that must be sent between parties in a distributed system to make it all work.

To support class (API) evolution the solution must also allow to provide "open ends" in the module dependency graph. The difference from PreferredClassProvider is that these "open ends" must allow selecting one of many existing class loaders instead of only one particular ClassLoader that a client has set as TCCL. TCCL is still needed to support many separate services in a single JVM. It is used to select a proper subset of class loaders as the set of candidates to choose from when resolving code bases. It allows to make sure that a single "static module" may produce many instances of "dynamic module" resolved differently in the same JVM.

I am working on this and hope to be able to provide an initial implementation soon. The solution I am working on assumes code bases are represented as serializable objects, so any example I am giving is based on that.

The basic idea might be presented as follows (of course details related to concurrency, weak references and proper method visibility to make the thing secure are left out):

class ClassResolver {
  static Map<ClassLoader, ClassResolver> globalResolverMap;
static ClassResolver getContextClassResolver() {...} //impl returns a resolver based on the TCCL

  final Map<ClassLoader, CodeBase> codeBaseMap;
  final Map<CodeBase, ClassLoader> existingLoadersMap;
  final Set<ApiCodeBase> apiImplementations;

  ClassLoader getClassLoader(CodeBase cb) {
    ClassLoader loader = lookup(cb);
    if (loader == null) {
      loader = resolve(cb).createLoader(this);
      //update the caches etc
    }
    return loader;
  }

  CodeBase resolve(CodeBase cb) {
    if (cb instanceof ApiCodeBase) {
      return resolveApi((ApiCodeBase) cb);
    }
    return cb;
  }

  CodeBase resolveApi(ApiCodeBase apiCb) {
    //java 8 style
//example is simplified since we want to select a "best match" in reality existingLoadersMap.keySet().stream().filter(apiCb::matchesCodeBase).first().orElse(apiCb);
  }
}

class CodeBase {
protected abstract ClassLoader createLoader(ClassResolver resolver); //creates a ClassLoader using provided resolver to resolve any dependencies
}

class ApiCodeBase {
  Predicate<? super CodeBase> matcher;
  CodeBase defaultImplementation;

  ClassLoader createLoader(ClassResolver resolver) {
    return defaultImplementation.createLoader(resolver);
  }

  boolean matchesCodeBase(CodeBase cb) {
    return matcher.test(cb);
  }

}

So now when a service provider creates initializes its runtime environment it creates a set of instances of CodeBase subclasses connected to each other in a way specific to a particular class loading implementation. Some of which might be ApiCodeBase instances that will use their default implementations to create a class loader in the service provider environment (since they are resolved in a "clean" ClassResolver on startup).

Any client that will deserialize an object graph provided by the service will either: 1. Not have matching CodeBase to select from when resolving ApiCodeBase (the situation similar to service provider startup)
2. Have a matching CodeBase to select from:
a) it is an ApiCodeBase - will possibly be again resolved when transferring further
b) it is not an ApiCodeBase - will "lock" an "open end"

So any party is free to mark modules as "private" or "public".

The tricky thing is still handling code base bounce backs.
If not careful when specifying API a service might encounter a "lost codebase" problem which might be mitigated by having API modules only consist of interfaces.

Thanks,
Michal

Gregg Wonderly wrote:
But codebase identity is not a single thing.  If you are going to allow a 
client to interact with multiple services and use those services, together, to 
create a composite service or just be a client application, you need all of the 
classes to interact.  One of the benefits of dynamic class loading and the 
selling point of how Jini was first presented (and I still consider this to the 
a big deal), is the notion that you can introduce a new version of a service 
which might already exist in duplicity to try out the new version.  Thus, the 
same class name can have multiple versions presented by multiple jars or 
bundles.  You need to load the right one, and expose it to the client(s) in a 
way that keeps things distinctly separated.

        service A ->  codeSource1, codesource2, codesource3
new service A ->  codesource4, codesource2, codesource5

If you get the new service A, you need (as if it was a separate service), to 
resolve it using the proper code sources.  I understand how to do this with 
TCCL, and I also understand how it might be done with some other class loading 
mechanism.  The question is, for OSGi bundles, how does a bundle loader manager 
make that any different from TCCL in that intimately, you still have a “tree” 
or “set” of dependencies that are resolved into the composite codebase.  
Bundles introduce a larger collection of active classes and mechanisms managing 
the dependency graph.  There are tools to assemble bundles and lots of other 
associated details.  TCCL makes it trivial to “know” what codesource is active 
and is no different in complexity then casting a class loader back to a bundle 
class loader to find fields which detail the collection of involved classes.

I am not an OSGi user.  I am not trying to be an OSGi opponent.  What I am 
trying to say is that I consider all the commentary in those articles about 
TCCL not working to be just inexperience and argument to try and justify a 
different position or interpretation of what the real problem is.

The real problem is that there is not one “module” concept in Java (another one 
is almost here in JDK 9/Jigsaw).  No one is working together on this, and OSGi 
is solving problems in a small part of the world of software.   It works well 
for embedded, static systems.  I think OSGi misses the mark on dynamic systems 
because of the piecemeal loading and resolving of classes.  I am not sure that 
OSGi developers really understand everything that Jini can do because of the 
choices made (and not made) in the design.  The people who put Jini together 
had a great deal of years of experience piecing together systems which needed 
to work well with a faster degree of variability and adaptation to the 
environment then what most people seem to experience in their classes and work 
environments which are locked down by extremely controlled distribution 
strategies which end up slowing development in an attempt to control everything 
that doesn’t actually cause quality to suffer.

Gregg



Reply via email to