Scott, Bela, and I have been discussing a solution to provide marshalling in the JBossCache layer about 3 weeks ago. But I was busy working on JBossCacheAop untill 2 days ago.
Now I am tackling this issue working through the design and initial implementation. I want to throw it out here to see if anyone has anything to add. BTW, if all go well, this is planned for release 1.2.4.
The main motive for this, of course, is now it is very common for JbossCache to run under a scoped class loader context. Case in point, in http session replication and SFSB replication, we all have the concept of scoped loader. However, plain JGroups replication will simply use the default system class loader to de-serialize the object. If application layer doesn't use MarshalledValue wrapper, we end up with ClassCastException!
Currently http and sfsb all use MarshalledValue in order to tackle this problem. However, there are issues:
1. JGroups will deserialize MarshalledValue first and the user do another MarshalledValue.get() to retrieve the underlying object.
2. In order to prevent from *un-marshalling* every single user get, we normally maintain a in-memory Pojo copy first. But then we will need an corresponding invlaidation mechanism to flush out the local copy in order to retrieve the updated version from MarshalledValue again. This is tedious to maintain and is not portable.
So here is the proposed solution. We will provide a TreeCacheMarshaller (under package org.jboss.cache.marshall) in JBossCache level. If it is activated, it will use user's specified class loader to de-serialize the key and value objects in the cache store. As a result, it can handle the scoped class loader automatically. In addition, there is also no need to maintain an in-memory copy.
It will implement a callback for RpcDispatcher.Marshaller from JGroups. So if it is registered, JGroups will delegate the de-sersialization to Marshaller. In order for this to work properly, we propose:
1. TreeCache provides registerClassLoader(Fqn fqn, ClassLoader cl) and
unregisterClassLoader(fqn fqn) apis to register the user-specific class loader.
2. User app when needed for marshalling, will register a ClassLoader with TreeCache under a "fqn" region. The concept of region is the same as in eviction. Any nodes fall under this fqn belongs to the same region.
3. During byteBufferFromObject api, we will extract the fqn from the MethodCall obejct, write it into output stream first before the regular one. Then during byteBufferToObject, we will read the fqn first to determine the corresponding class loader before handling the de-serialization.
For example, in http session replication, we would register the "webpath" as a region since it is unique for each web app. And so naturally every session under that will use the same class loader.
But here is one restriction. Since replicated MethodCalls include put, remove, prepare, commit, and rollback, we require all these calls operate under the same "region" for marshalling to work. This is probably ok (e.g., http and sfsb).
There is also a known issue in http session replication. Currently we also externalize the session in order to keep the same session instance. So we will need to come up a way to prevent duplication of serialization.
In jboss-serialization I created such objectFromByteArray and byteArrayFromobject.
I think it would be nice if we could have a pluggable way to use jboss-serialization in JbossCache. Remoting will have a way to add plugable marshallers, maybe we could do something generic together.
Let's chat then. I have mentioned here that I need to extract the fqn feild and put it in the payload during objectToByteBuffer, such that I can obtain the class loader information during the objectFromByteBuffer stage.
That is, user will need to associate (fqn, cl) first for it to work.
This is specific to JBossCache. But there may be a generic way of doing it.
Do you envisage having different classloaders for subtrees of subtrees ? E.g.
/a/b/c: use CL1
/a/b/c/e/f: use CL2
So, in other words, can a user override the CL for a given subtree, like in eviction policies ?
Yes, the semantics is exactly like that of the eviction policy one.
I realized that there is also a need for (un)marshalling of the initial state transfer. Use case being that when a new node joins the cluster group, all the new apps that got deployed will need to specify their corresponding class loaders before the initial state transfer taking place.
Towards that end, two tasks are needed:
1. Marshall/unmarshall the state transfer. Currently the state transfer consists of transient and persistent state. Both have different schema. So we will need to bridge that.
2. Refactor the cache lifecycle. We will need to move the code from startService to createService. So startService is really stuffs like channel.connect() to kick start the cache (and therefore, the state transfer). In other words, after createService, user can go ahead and register their (region, cl) pair so that during startService, cache knows how to unmarshall the state accoding to fqn.