Open task on AOP and TreeCache - volunteers ?
belaban Apr 25, 2003 12:48 PMHi all,
I have an open task, the scope of which is fairly well-defined, and I believe it is very interesting work. I'm posting this job because I currently lack the time to do it myself, and because it is a fairly self-contained job, so it won't interfere with my other cache work.
The task is described below and is also in the CVS at jboss-head/cache/docs/TreeCacheAop.txt.
This is an opportunity to get involved and get to know (a) the new replicated cache and (b) the AOP framework in JBoss as implemented by Bill. You will be working on the bleeding edge :-)
Requirements:
- knowledge of reflection API
- some knowledge of JBoss architecture
- knowledge of or willingness to learn the AOP framework
- knowledge of or willingness to learn the Cache framework (org.jboss.cache.TreeCache)
Please contact me if you're interested.
AOP-enhanced TreeCache
======================
Author: Bela Ban (bela@jboss.org)
Revision: $Id: TreeCacheAop.txt,v 1.5 2003/04/24 23:45:26 belaban Exp $
Goal:
-----
Put any plain old Java object into the TreeCache and have it
replicated transactionally (or however the cache is configured) across
nodes.
Any change to the object should not require an app programmer to tell
the cache, in order for the cache to replicate the
modification. Instead, the cache 'knows' (using AOP) when the object
has been modified, and what fields have been changed.
Task:
-----
Provide a subclass org.jboss.cache.TreeCacheAop (skeleton already
exists) of org.jboss.cache.TreeCache.
Add methods putObject(String fqn, Object pojo), getObject(String fqn)
and removeObject(String fqn) to TreeCacheAop. These are the main
methods.
Users who want to use the AOP-enhanced cache can instantiate
TreeCacheAop, rather than TreeCache. I may merge those 2 classes at
some later time. But currently this allows me to separate non-AOP and
AOP code.
All objects to be put into the cache must be instrumented (see AOP XML
files).
Whenever an object is added to the cache, putObject() needs to do the
following.
First it needs to cast the object to Advisable, get the list of
interceptors and add itself (CacheInterceptor) to the list (if not yet
there).
A CacheInterceptor either intercepts all set*() and get*() methods
(using the setter/getter paradigm used e.g. for entity beans in CMP
2.0), or intercepts all field access (reads and writes)
When a CacheInterceptor is created, it is given the location in the
tree where the object resides, e.g. "/persons/322649".
Then, the putObject() method needs to map the fields of the object to
the cache: e.g. "age" and "name" become keys in the "/persons/322649"
node's hashmap (see TreeCache javadoc for details about the structure
of the TreeCache). This obviously requires reflection (or maybe AOP as
well).
Whenever a read is intercepted (e.g. getter method getAge() or field
access get("age")), we return the value associated with the key in the
hashmap.
Whenever a field changes (setter method or field access), we modify
the value in the hashmap.
When getObject(String fqn) is called, we locate the node in the
tree. Each object has its fully qualified classname in the hashmap
associated with that node. We create an instance of that class (object
has to have a null constructor), and set all fields recursively using
reflection and the values in the tree.
When removeObject() is called, we remove the object from the cache,
and also remove our interceptor from its interceptor chain.
Instrumenting object graphs:
----------------------------
When an object has non-primitive fields, e.g. Address and LinkedList,
we will need to recursively add interceptors for those objects as
well.
E.g. when the Person object has a field "addr" of type Address, we
will need to add an interceptor to the "addr" object whenever it is
set. That interceptor also needs to 'know' where in the tree the
"addr" object is located, e.g. "/persons/322649/addr".
If any field inside the "addr" object changes (e.g.
p.getAddress().setCity("San Jose CA")), the interceptor needs to
update the corresponding value in that node's hashmap, e.g.
super.put("/persons/322649/addr", "city", "San Jose"). The cache will
take care of acidity and replication.
For system classes such as java.util.LinkedList etc, we will need to
use either (a) ClassProxies or (b) modify the ClassPool directly a la
Bill Burke's proposal.
Example of mapped object:
-------------------------
/persons/322649:
- "class_name_unique" --> "my.package.Person
- "age" --> 38 (Long)
- "name" --> "Bela Ban" (String)
/persons/322649/addr:
- "class_name_unique" --> my.package.Address
- "city" --> "San Jose CA"
- "street" --> "1704 Almond Blossom Lane"
Optimistic locking (versioning):
--------------------------------
(pasted from an email conversation with Bill Burke)
I'm trying to get my head around how interceptors could be used in the
TreeCache for hanslding an aspect such as locking. I looked at (a)
interceptors for each object in the cache and (b) interceptors for the
cache (TreeCache MBean) itself.
I couldn't find a solution, have to invest some more time. It would be nice if we could extract the 'locking' aspect into interceptors.
What I *did* come up with, however, is an easy scheme how to add versioning to the current TreeCache:
* In findNode(String fqn), instead of locking the path to the node,
I create a copy of the node in a versioned workspace (if it
doesn't yet exist). Otherwise I just return the Node from the
workspace associated with the TX
* On commit, I do *exactly the same I already do*: I ship all
updates to all nodes in a PREPARE phase and on success send a
COMMIT. The one change is that before applying an update I have to
check for the version ID and acquire the lock. In my scheme, it is
just lock acquisition, no version check.
Maybe I'm missing something, but this seems trivial. I guess the trick
is to avoid copying too much...