package org.jboss.cache.aop;
import org.jboss.aop.Advised;
import org.jboss.aop.ClassInstanceAdvisor;
import org.jboss.aop.InstanceAdvisor;
import org.jboss.aop.advice.Interceptor;
import org.jboss.aop.proxy.ClassProxy;
import org.jboss.cache.*;
import org.jboss.cache.transaction.BatchModeTransactionManager;
import org.jboss.cache.eviction.AopEvictionPolicy;
import org.jboss.util.NestedRuntimeException;
import org.jgroups.JChannel;
import javax.transaction.*;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.*;
public class TreeCacheAop extends TreeCache implements TreeCacheAopMBean
{
protected Map cachedTypes = new WeakHashMap();
public static final String CLASS_INTERNAL = "jboss:internal:class";
public static final Fqn JBOSS_INTERNAL = new Fqn("JBossInternal");
public static final String DUMMY = "dummy";
TransactionManager localTm_ = BatchModeTransactionManager.getInstance();
public TreeCacheAop(String cluster_name,
String props,
long state_fetch_timeout)
throws Exception
{
super(cluster_name, props, state_fetch_timeout);
}
public TreeCacheAop() throws Exception
{
}
public TreeCacheAop(JChannel channel) throws Exception
{
super(channel);
}
public void setEvictionPolicyClass(String eviction_policy_class) {
this.eviction_policy_class=eviction_policy_class;
if(eviction_policy_class == null || eviction_policy_class.length() ==0)
return;
try {
Object obj = getClass().getClassLoader().loadClass(eviction_policy_class).newInstance();
if( ! (obj instanceof AopEvictionPolicy))
throw new RuntimeException("TreeCacheAop.setEvictionPolicyClass(). Eviction policy provider:" +
eviction_policy_class +" is not an instance of AopEvictionPolicy.");
eviction_policy_provider =(TreeCacheListener) obj;
this.addTreeCacheListener(eviction_policy_provider );
}
catch(RuntimeException ex) {
log.error("setEvictionPolicyClass(): failed creating instance of " + eviction_policy_class, ex);
throw ex;
}
catch(Throwable t) {
log.error("setEvictionPolicyClass(): failed creating instance of " + eviction_policy_class, t);
}
}
public Object putObject(String fqn, Object obj) throws CacheException
{
return putObject(Fqn.fromString(fqn), obj);
}
public Object putObject(Fqn fqn, Object obj) throws CacheException
{
if( hasCurrentTransaction() ) {
return _putObject(fqn, obj);
} else
{
try
{
localTm_.begin();
put(fqn, DUMMY, DUMMY);
Object objOld = _putObject(fqn,obj);
remove(fqn, DUMMY);
return objOld;
}
catch (Exception e)
{
e.printStackTrace();
try
{
localTm_.setRollbackOnly();
}
catch (Exception exn)
{
exn.printStackTrace();
}
throw new NestedRuntimeException("TreeCacheAop.putObject(): ", e);
}
finally
{
endTransaction();
}
}
}
protected void endTransaction()
{
try {
if(localTm_.getTransaction().getStatus() != Status.STATUS_MARKED_ROLLBACK)
{
localTm_.commit();
} else
{
localTm_.rollback();
}
} catch (Exception e) {
e.printStackTrace();
throw new NestedRuntimeException("TreeCacheAop.endTransaction(): ", e);
}
}
public Object _putObject(Fqn fqn, Object obj) throws CacheException
{
checkObjectType(obj);
AOPInstance aopInstance = (AOPInstance) peek(fqn, AOPInstance.KEY);
boolean isSameClass = false;
Object oldValue = null;
if(log.isDebugEnabled()) {
log.debug("putObject(): fqn: " + fqn);
}
if (aopInstance != null) {
if (aopInstance.get() == obj) {
return obj;
}
if (obj == null) {
return removeObject(fqn);
}
log.debug("putObject(): old class type: " + aopInstance.get());
if (obj.getClass().isInstance(aopInstance.get())) {
if (log.isDebugEnabled()) {
log.debug("putObject(): obj is same class type as the old one");
}
oldValue = getObject(fqn);
isSameClass = true;
} else {
oldValue = removeObject(fqn);
}
} else {
Class claz1 = (Class) peek(fqn, CLASS_INTERNAL);
if (claz1 != null &&
obj.getClass().getName().equals(claz1.getName())) {
if (log.isDebugEnabled()) {
log.debug("putObject(): obj is same class type as the old one");
}
isSameClass = true;
}
}
if (obj == null) { return null;
}
if (obj instanceof Advised) {
CachedType type = getCachedType(obj.getClass());
Fqn internalFqn = null;
InstanceAdvisor advisor = ((Advised) obj)._getInstanceAdvisor();
if ((internalFqn = checkCircularReference(fqn, advisor, obj)) != null) {
removeObject(fqn);
aopInstance = new AOPInstance();
aopInstance.setRefFqn(internalFqn.toString());
put(fqn, AOPInstance.KEY, aopInstance);
put(fqn, CLASS_INTERNAL, type.getType());
return oldValue;
} else if ((internalFqn = handleObjectGraph(fqn, advisor, type, obj)) != null) { if (log.isDebugEnabled()) {
log.debug("putObject(): detected multiple references. Will use as a reference instead. Current fqn: "
+ fqn + " Reference fqn: " + internalFqn);
}
removeObject(fqn);
aopInstance = new AOPInstance();
setRefFqn(aopInstance, internalFqn.toString());
put(fqn, AOPInstance.KEY, aopInstance);
put(fqn, CLASS_INTERNAL, type.getType());
return oldValue;
}
if (advisor == null) {
advisor = new ClassInstanceAdvisor(obj);
((Advised) obj)._setInstanceAdvisor(advisor);
}
advisor.appendInterceptor(new CacheInterceptor(this, fqn, type));
put(fqn, CLASS_INTERNAL, type.getType());
for (Iterator i = type.getFields().iterator(); i.hasNext();) {
Field field = (Field) i.next();
Object value = null;
try {
value=field.get(obj);
}
catch(IllegalAccessException e) {
throw new CacheException("field access failed", e);
}
CachedType fieldType = getCachedType(field.getType());
if (fieldType.isImmediate()) {
put(fqn, field.getName(), value);
} else {
Fqn tmpFqn = new Fqn(fqn, field.getName());
_putObject(tmpFqn, value);
if( value instanceof Map || value instanceof List || value instanceof Set ) {
Object newValue = getObject(tmpFqn);
try {
field.set(obj, newValue);
} catch (IllegalAccessException e) {
log.error("_putObject(): Can't swap out the Collection class of field " +field.getName() +
"Exception " +e);
throw new CacheException("_putObject(): Can't swap out the Collection class of field \" +field.getName(),"
+e);
}
}
}
}
put(fqn, AOPInstance.KEY, new AOPInstance(obj));
if (log.isDebugEnabled()) {
log.debug("putObject(): inserting with fqn: " + fqn.toString());
}
} else if (obj instanceof Map) {
if (log.isDebugEnabled()) {
log.debug("putObject(): aspectized obj is a Map type of size: " + ((Map) obj).size());
}
removeObject(fqn);
put(fqn, CLASS_INTERNAL, obj.getClass());
Object oldObj = obj;
if( !(obj instanceof ClassProxy)) {
Class clazz = obj.getClass();
try {
obj=CollectionInterceptorUtil.createProxy(clazz, new CachedMapInterceptor(this, fqn, clazz));
} catch (Exception e) {
throw new CacheException("failure creating proxy", e);
}
}
Map map = (Map) oldObj;
for (Iterator i = map.entrySet().iterator(); i.hasNext();) {
Map.Entry entry = (Map.Entry) i.next();
_putObject(new Fqn(fqn, entry.getKey()), entry.getValue());
}
put(fqn, AOPInstance.KEY, new AOPInstance(obj));
} else if (obj instanceof List) {
if (log.isDebugEnabled()) {
log.debug("putObject(): aspectized obj is a List type of size: "
+ ((List) obj).size());
}
List list = (List) obj;
removeObject(fqn);
put(fqn, CLASS_INTERNAL, obj.getClass());
if( !(obj instanceof ClassProxy)) {
Class clazz = obj.getClass();
try {
obj=CollectionInterceptorUtil.createProxy(clazz, new CachedListInterceptor(this, fqn, clazz));
} catch (Exception e) {
throw new CacheException("failure creating proxy", e);
}
}
int idx = 0;
for (Iterator i = list.iterator(); i.hasNext();) {
_putObject(new Fqn(fqn, new Integer(idx++)), i.next());
}
put(fqn, AOPInstance.KEY, new AOPInstance(obj));
} else if (obj instanceof Set) {
if (log.isDebugEnabled()) {
log.debug("putObject(): aspectized obj is a Set type of size: "
+ ((Set) obj).size());
}
Set set = (Set) obj;
removeObject(fqn);
put(fqn, CLASS_INTERNAL, obj.getClass());
if( !(obj instanceof ClassProxy)) {
Class clazz = obj.getClass();
try {
obj=CollectionInterceptorUtil.createProxy(clazz, new CachedSetInterceptor(this, fqn, clazz));
} catch (Exception e) {
throw new CacheException("failure creating proxy", e);
}
}
int idx = 0;
for (Iterator i = set.iterator(); i.hasNext();) {
_putObject(new Fqn(fqn, new Integer(idx++)), i.next());
}
put(fqn, AOPInstance.KEY, new AOPInstance(obj));
} else if (obj instanceof Serializable) { if (log.isDebugEnabled()) {
log.debug("putObject(): obj (" + obj.getClass() + ") is non-advisable but is Serializable. ");
}
if (!isSameClass)
put(fqn, CLASS_INTERNAL, obj.getClass());
put(fqn, AOPInstance.KEY, new AOPInstance(obj));
put(fqn, "value", obj);
}
return oldValue;
}
protected void checkObjectType(Object obj) {
if(obj == null) return;
if( ! (obj instanceof Advised) ) {
if( !(obj instanceof Serializable ) ) {
throw new IllegalArgumentException("TreeCacheAop.putObject(): Object type is neither " +
" aspectized nor Serializable. Object class name is " +obj.getClass().getName());
}
}
}
protected Fqn checkCircularReference(Fqn fqn, InstanceAdvisor advisor, Object obj)
{
if (advisor == null) return null;
Fqn originalFqn = null;
org.jboss.aop.advice.Interceptor interceptor = findCacheInterceptor(advisor);
if (interceptor == null) {
return null;
}
originalFqn = ((CacheInterceptor) interceptor).getFqn();
if (fqn.isChildOf(originalFqn)) {
if (log.isDebugEnabled()) {
log.debug("checkCircularReference(): is child for circular ref fqn " + originalFqn);
}
return originalFqn;
}
return null;
}
protected Fqn handleObjectGraph(Fqn fqn, InstanceAdvisor advisor, CachedType type, Object obj) throws CacheException {
if (advisor == null) return null;
Fqn originalFqn = null;
Fqn internalFqn = null;
org.jboss.aop.advice.Interceptor interceptor = findCacheInterceptor(advisor);
if (interceptor == null) {
if (log.isDebugEnabled()) {
log.debug("handleMultipleReference(): No multiple refernce found for fqn: " + fqn);
}
return null;
}
originalFqn = ((CacheInterceptor) interceptor).getFqn();
if( fqn.equals(originalFqn) ) return null;
if (log.isDebugEnabled()) {
log.debug("handleObjectGraph(): Found multiple refernce at original fqn: " + originalFqn);
}
if (originalFqn.isChildOf(JBOSS_INTERNAL)) {
if (log.isDebugEnabled()) {
log.debug("handleObjectGraph(): is child for fqn " + originalFqn);
}
return originalFqn;
} else {
internalFqn = createInternalNode(originalFqn);
Object oldValue = removeObject(originalFqn);
putObject(internalFqn, obj);
AOPInstance instance = new AOPInstance();
setRefFqn(instance, internalFqn.toString());
put(originalFqn, AOPInstance.KEY, instance); put(originalFqn, CLASS_INTERNAL, type.getType());
if (log.isDebugEnabled()) {
log.debug("handleObjectGraph(): relocate the original fqn: " + originalFqn +
" to JBossInternal: " + internalFqn + " with obj: " + oldValue);
}
return internalFqn;
}
}
protected Interceptor findCacheInterceptor(InstanceAdvisor advisor, Fqn fqn)
{
org.jboss.aop.advice.Interceptor[] interceptors = advisor.getInterceptors();
for (int i = 0; i < interceptors.length; i++) {
Interceptor interceptor = interceptors[i];
if (interceptor instanceof CacheInterceptor) {
CacheInterceptor inter = (CacheInterceptor)interceptor;
if (inter != null && inter.getFqn().equals(fqn))
{
return interceptor;
}
}
}
return null;
}
protected Interceptor findCacheInterceptor(InstanceAdvisor advisor)
{
org.jboss.aop.advice.Interceptor[] interceptors = advisor.getInterceptors();
for (int i = 0; i < interceptors.length; i++) {
Interceptor interceptor = interceptors[i];
if (interceptor instanceof CacheInterceptor) {
return interceptor;
}
}
return null;
}
protected Fqn createInternalNode(Fqn storedFqn)
{
Fqn fqn = new Fqn(JBOSS_INTERNAL, storedFqn);
return fqn;
}
public Object getObject(String fqn) throws CacheException
{
return getObject(Fqn.fromString(fqn));
}
protected boolean hasCurrentTransaction()
{
try {
if(getCurrentTransaction() != null || localTm_.getTransaction() != null)
{
return true;
}
} catch (SystemException e) {
e.printStackTrace();
}
return false;
}
public Object getObject(Fqn fqn) throws CacheException
{
if( hasCurrentTransaction() ) {
Object obj = _getObject(fqn);
return obj;
} else
{
try
{
localTm_.begin();
put(fqn, DUMMY, DUMMY);
Object obj = _getObject(fqn);
remove(fqn, DUMMY);
return obj;
}
catch (Exception e)
{
e.printStackTrace();
try
{
localTm_.setRollbackOnly();
}
catch (Exception exn)
{
exn.printStackTrace();
}
throw new NestedRuntimeException("TreeCacheAop.getObject(): ", e);
}
finally
{
endTransaction();
}
}
}
private Object _getObject(Fqn fqn) throws CacheException
{
AOPInstance aopInstance = (AOPInstance) peek(fqn, AOPInstance.KEY);
if (aopInstance != null && aopInstance.get() != null) {
return aopInstance.get();
}
Class clazz = (Class) peek(fqn, CLASS_INTERNAL);
if (clazz == null || aopInstance == null)
return null;
CachedType type = getCachedType(clazz);
Object obj = null;
if (Advised.class.isAssignableFrom(clazz)) {
String refFqn = aopInstance.getRefFqn();
if (refFqn == null) { try {
obj = clazz.newInstance();
}
catch(Exception e) {
throw new CacheException("failed creating instance of " + clazz.getName(), e);
}
InstanceAdvisor advisor = ((Advised) obj)._getInstanceAdvisor();
advisor.appendInterceptor(new CacheInterceptor(this, fqn, type));
} else {
if (log.isDebugEnabled()) {
log.debug("getObject(): obtain value from reference fqn: " + refFqn);
}
obj = getObject(refFqn);
}
} else { try {
if(Map.class.isAssignableFrom(clazz)) {
obj=CollectionInterceptorUtil.createProxy(clazz, new CachedMapInterceptor(this, fqn, clazz));
} else if(List.class.isAssignableFrom(clazz)) {
obj=CollectionInterceptorUtil.createProxy(clazz, new CachedListInterceptor(this, fqn, clazz));
} else if(Set.class.isAssignableFrom(clazz)) {
obj=CollectionInterceptorUtil.createProxy(clazz, new CachedSetInterceptor(this, fqn, clazz));
} else {
obj=peek(fqn, "value");
}
}
catch(Exception e) {
throw new CacheException("failure creating proxy", e);
}
}
if (aopInstance == null) {
throw new RuntimeException("getObject(): aopInstance is null");
}
aopInstance.set(obj);
return obj;
}
public Object removeObject(String fqn) throws CacheException
{
return removeObject(Fqn.fromString(fqn));
}
public Object removeObject(Fqn fqn) throws CacheException
{
if( hasCurrentTransaction() ) {
return _removeObject(fqn, true);
} else
{
try
{
localTm_.begin();
put(fqn, DUMMY, DUMMY);
Object obj = _removeObject(fqn, true);
remove(fqn, DUMMY);
return obj;
}
catch (Exception e)
{
e.printStackTrace();
try
{
localTm_.setRollbackOnly();
}
catch (Exception exn)
{
exn.printStackTrace();
}
throw new NestedRuntimeException("TreeCacheAop.removeObject(): ", e);
}
finally
{
endTransaction();
}
}
}
protected Object _removeObject(Fqn fqn, boolean removeCacheInterceptor) throws CacheException
{
if (log.isDebugEnabled()) {
log.debug("removeObject(): removing object from fqn: " + fqn);
}
Class clazz = (Class) peek(fqn, CLASS_INTERNAL);
if (clazz == null)
{
if (log.isTraceEnabled()) {
log.trace("removeObject(): clasz is null. fqn: " + fqn);
}
return null;
}
Object result = getObject(fqn);
AOPInstance aopInstance = (AOPInstance) peek(fqn, AOPInstance.KEY);
if (Advised.class.isAssignableFrom(clazz)) {
String refFqn = aopInstance.getRefFqn();
InstanceAdvisor advisor = ((Advised) result)._getInstanceAdvisor();
if (refFqn != null) {
if (log.isDebugEnabled()) {
log.debug("removeObject(): removing object fqn: " + fqn + " but is actually from ref fqn: " + refFqn);
}
removeRefFqn(aopInstance, refFqn, removeCacheInterceptor);
} else {
CachedType type = getCachedType(clazz);
for (Iterator i = type.getFields().iterator(); i.hasNext();) {
Field field = (Field) i.next();
CachedType fieldType = getCachedType(field.getType());
if (!fieldType.isImmediate()) {
Object obj = _removeObject(new Fqn(fqn, field.getName()), removeCacheInterceptor);
if (obj == null) continue;
}
}
if(removeCacheInterceptor) {
CacheInterceptor interceptor = (CacheInterceptor) findCacheInterceptor(advisor);
if (interceptor != null)
{
if (log.isDebugEnabled()) {
log.debug("removeObject(): removed cache interceptor fqn: " + fqn + " interceptor: "+interceptor);
}
advisor.removeInterceptor(interceptor.getName());
}
}
}
} else if (Map.class.isAssignableFrom(clazz)) {
Map values = get(fqn).getChildren();
if (values != null) {
ArrayList list = new ArrayList(values.keySet()); for (int i=0; i < list.size(); i++) {
Object key = list.get(i);
_removeObject(new Fqn(fqn, key), removeCacheInterceptor);
}
}
} else if (Collection.class.isAssignableFrom(clazz)) {
Map values = get(fqn).getChildren();
int size = values == null ? 0 : values.size();
for (int i = 0; i < size; i++) {
_removeObject(new Fqn(fqn, new Integer(i)), removeCacheInterceptor);
}
}
remove(fqn);
return result;
}
public synchronized CachedType getCachedType(Class clazz)
{
CachedType type = (CachedType) cachedTypes.get(clazz);
if (type == null) {
type = new CachedType(clazz);
cachedTypes.put(clazz, type);
}
return type;
}
void setRefFqn(AOPInstance instance, String refFqn) throws CacheException {
AOPInstance refInstance = (AOPInstance) peek(Fqn.fromString(refFqn), AOPInstance.KEY);
synchronized (this) {
refInstance.incrementRefCount();
put(refFqn, AOPInstance.KEY, refInstance);
}
instance.setRefFqn(refFqn);
}
void removeRefFqn(AOPInstance instance, String refFqn, boolean removeCacheInterceptor) throws CacheException {
AOPInstance refInstance = (AOPInstance) peek(Fqn.fromString(refFqn), AOPInstance.KEY);
synchronized (this) {
if (refInstance.decrementRefCount() == 0) {
_removeObject(Fqn.fromString(refFqn), removeCacheInterceptor);
} else {
put(refFqn, AOPInstance.KEY, refInstance);
}
}
instance.removeRefFqn();
}
public Object _put(GlobalTransaction tx, Fqn fqn, Object key, Object value,
boolean create_undo_ops) throws CacheException
{
Object result = super._put(tx, fqn, key, value, create_undo_ops);
if (key == AOPInstance.KEY)
return result;
AOPInstance aopInstance = (AOPInstance) _get(fqn, AOPInstance.KEY, false);
Object instance = null;
if (aopInstance == null || (instance = aopInstance.get()) == null) {
return result;
}
CachedType type = getCachedType((Class) _get(fqn, CLASS_INTERNAL, false));
if(type.isImmediate()) return result;
Field f = type.getField((String) key);
if(f == null) return result;
try {
f.set(instance, value);
} catch (Exception e) {
throw new NestedRuntimeException(e);
}
return result;
}
public void evict(Fqn fqn) throws CacheException {
if(isAopNode(fqn)) {
if(log.isDebugEnabled()) {
log.debug("evict(): evicting whole aop node " +fqn);
}
boolean removeCacheInterceptor = false;
_removeObject(fqn, removeCacheInterceptor);
} else {
super.evict(fqn);
}
}
private boolean isAopNode(Fqn fqn)
{
try {
return (peek(fqn, AOPInstance.KEY) != null) ? true: false;
} catch (Exception e) {
log.warn("isAopNode(): cache get operation generated exception " +e);
return false;
}
}
}