package org.jboss.cache.interceptors;
import org.jboss.cache.Fqn;
import org.jboss.cache.InvocationContext;
import org.jboss.cache.Modification;
import org.jboss.cache.Node;
import org.jboss.cache.NodeSPI;
import org.jboss.cache.marshall.MethodCall;
import org.jboss.cache.marshall.MethodDeclarations;
import org.jboss.cache.transaction.GlobalTransaction;
import org.jboss.cache.transaction.TransactionEntry;
import org.jboss.cache.transaction.TransactionTable;
import org.jgroups.Address;
import javax.transaction.SystemException;
import javax.transaction.TransactionManager;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Loads nodes that don't exist at the time of the call into memory from the CacheLoader.
* If the nodes were evicted earlier then we remove them from the cache loader after
* their attributes have been initialized and their children have been loaded in memory.
*
* @author <a href="mailto:{hmesha@novell.com}">{Hany Mesha}</a>
* @version $Id: ActivationInterceptor.java 4986 2008-01-04 16:55:01Z manik.surtani@jboss.com $
*/
public class ActivationInterceptor extends CacheLoaderInterceptor implements ActivationInterceptorMBean
{
protected TransactionManager tx_mgr = null;
protected TransactionTable tx_table = null;
private HashMap m_txActivations = new HashMap();
private long m_activations = 0;
/**
* List<Transaction> that we have registered for
*/
protected ConcurrentHashMap transactions = new ConcurrentHashMap(16);
protected static final Object NULL = new Object();
public ActivationInterceptor()
{
initLogger();
isActivation = true;
useCacheStore = false;
}
@Override
protected Object handleRemoveDataMethod(InvocationContext ctx, GlobalTransaction tx, Fqn fqn, boolean createUndoOps) throws Throwable
{
Object returnValue = super.handleRemoveDataMethod(ctx, tx, fqn, createUndoOps);
if (trace)
log.trace("This is a remove data operation; removing the data from the loader, no activation processing needed.");
loader.removeData(fqn);
return returnValue;
}
@Override
protected Object handleRemoveNodeMethod(InvocationContext ctx, GlobalTransaction tx, Fqn fqn, boolean createUndoOps) throws Throwable
{
Object returnValue = super.handleRemoveNodeMethod(ctx, tx, fqn, createUndoOps);
if (trace)
log.trace("This is a remove operation; removing the node from the loader, no activation processing needed.");
loader.remove(fqn);
return returnValue;
}
@Override
protected Object handlePrintMethod(InvocationContext ctx, Fqn fqn) throws Throwable
{
Object returnValue = super.handlePrintMethod(ctx, fqn);
removeNodeFromCacheLoader(ctx, fqn);
return returnValue;
}
@Override
protected Object handleReleaseAllLocksMethod(InvocationContext ctx, Fqn fqn) throws Throwable
{
Object returnValue = super.handleReleaseAllLocksMethod(ctx, fqn);
removeNodeFromCacheLoader(ctx, fqn);
return returnValue;
}
@Override
protected Object handleGetChildrenNamesMethod(InvocationContext ctx, Fqn fqn) throws Throwable
{
Object returnValue = super.handleGetChildrenNamesMethod(ctx, fqn);
removeNodeFromCacheLoader(ctx, fqn);
return returnValue;
}
@Override
protected Object handleGetKeysMethod(InvocationContext ctx, Fqn fqn) throws Throwable
{
Object returnValue = super.handleGetKeysMethod(ctx, fqn);
removeNodeFromCacheLoader(ctx, fqn);
return returnValue;
}
@Override
protected Object handleGetNodeMethod(InvocationContext ctx, Fqn fqn) throws Throwable
{
Object returnValue = super.handleGetNodeMethod(ctx, fqn);
removeNodeFromCacheLoader(ctx, fqn);
return returnValue;
}
@Override
protected Object handleGetKeyValueMethod(InvocationContext ctx, Fqn fqn, Object key, boolean sendNodeEvent) throws Throwable
{
Object returnValue = super.handleGetKeyValueMethod(ctx, fqn, key, sendNodeEvent);
removeNodeFromCacheLoader(ctx, fqn);
return returnValue;
}
@Override
protected Object handleAddChildMethod(InvocationContext ctx, GlobalTransaction tx, Fqn parentFqn, Object childName, Node cn, boolean createUndoOps) throws Throwable
{
Object returnValue = super.handleAddChildMethod(ctx, tx, parentFqn, childName, cn, createUndoOps);
removeNodeFromCacheLoader(ctx, parentFqn);
return returnValue;
}
@Override
protected Object handlePutForExternalReadMethod(InvocationContext ctx, GlobalTransaction tx, Fqn fqn, Object key, Object value) throws Throwable
{
Object returnValue = super.handlePutForExternalReadMethod(ctx, tx, fqn, key, value);
removeNodeFromCacheLoader(ctx, fqn);
return returnValue;
}
@Override
protected Object handlePutDataMethod(InvocationContext ctx, GlobalTransaction tx, Fqn fqn, Map data, boolean createUndoOps) throws Throwable
{
Object returnValue = super.handlePutDataMethod(ctx, tx, fqn, data, createUndoOps);
removeNodeFromCacheLoader(ctx, fqn);
return returnValue;
}
@Override
protected Object handlePutKeyValueMethod(InvocationContext ctx, GlobalTransaction gtx, Fqn fqn, Object key, Object value, boolean createUndoOps) throws Throwable
{
Object returnValue = super.handlePutKeyValueMethod(ctx, gtx, fqn, key, value, createUndoOps);
removeNodeFromCacheLoader(ctx, fqn);
return returnValue;
}
@Override
protected Object handleRemoveKeyMethod(InvocationContext ctx, GlobalTransaction tx, Fqn fqn, Object key, boolean createUndoOps) throws Throwable
{
Object returnValue = super.handleRemoveKeyMethod(ctx, tx, fqn, key, createUndoOps);
removeNodeFromCacheLoader(ctx, fqn);
return returnValue;
}
@Override
protected Object handlePutDataEraseMethod(InvocationContext ctx, GlobalTransaction gt, Fqn fqn, Map newData, boolean createUndoOps, boolean eraseContents) throws Throwable
{
Object returnValue = super.handlePutDataEraseMethod(ctx, gt, fqn, newData, createUndoOps, eraseContents);
removeNodeFromCacheLoader(ctx, fqn);
return returnValue;
}
/**
* Remove the node from the cache loader if it exists in memory,
* its attributes have been initialized, its children have been loaded,
* AND it was found in the cache loader (nodeLoaded = true).
* Then notify the listeners that the node has been activated.
*/
private void removeNodeFromCacheLoader(InvocationContext ctx, Fqn fqn) throws Throwable
{
NodeSPI n;
if (((n = peekNode(ctx, fqn, false, true, false)) != null) && n.isDataLoaded() && loader.exists(fqn))
{
// node not null and attributes have been loaded?
if (!n.getChildrenDirect().isEmpty())
{
if (allInitialized(n))
{
log.debug("children all initialized");
remove(ctx, fqn);
}
}
else if (loaderNoChildren(fqn))
{
if (log.isDebugEnabled()) log.debug("no children " + n);
remove(ctx, fqn);
}
}
}
@Override
protected Object handleOptimisticPrepareMethod(InvocationContext ctx, GlobalTransaction gtx, List modifications, Map data, Address address, boolean onePhaseCommit) throws Throwable
{
Object retval = nextInterceptor(ctx);
if (inTransaction())
{
prepareCacheLoader(ctx);
}
return retval;
}
private boolean inTransaction()
throws SystemException
{
return tx_mgr != null && tx_mgr.getTransaction() != null;
}
@Override
protected Object handlePrepareMethod(InvocationContext ctx, GlobalTransaction gtx, List modification, Address coordinator, boolean onePhaseCommit) throws Throwable
{
Object retval = nextInterceptor(ctx);
if (inTransaction())
{
prepareCacheLoader(ctx);
}
return retval;
}
private void remove(InvocationContext ctx, Fqn fqn) throws Exception
{
cache.getNotifier().notifyNodeActivated(fqn, true, Collections.emptyMap(), ctx);
loader.remove(fqn);
if (configuration.getExposeManagementStatistics() && getStatisticsEnabled())
{
m_activations++;
}
}
/**
* Returns true if a node has all children loaded and initialized.
*/
private boolean allInitialized(NodeSPI<?, ?> n)
{
if (!n.isChildrenLoaded())
{
return false;
}
for (NodeSPI child : n.getChildrenDirect())
{
if (!child.isDataLoaded())
{
return false;
}
}
return true;
}
/**
* Returns true if the loader indicates no children for this node.
* Return false on error.
*/
private boolean loaderNoChildren(Fqn fqn)
{
try
{
Set children_names = loader.getChildrenNames(fqn);
return (children_names == null);
}
catch (Exception e)
{
log.error("failed getting the children names for " + fqn + " from the cache loader", e);
return false;
}
}
public long getActivations()
{
return m_activations;
}
@Override
public void resetStatistics()
{
super.resetStatistics();
m_activations = 0;
}
@Override
public Map<String, Object> dumpStatistics()
{
Map<String, Object> retval = super.dumpStatistics();
if (retval == null)
{
retval = new HashMap<String, Object>();
}
retval.put("Activations", m_activations);
return retval;
}
private void prepareCacheLoader(InvocationContext ctx) throws Exception
{
List<MethodCall> modifications;
TransactionEntry entry;
int txActs = 0;
GlobalTransaction gtx = ctx.getGlobalTransaction();
entry = tx_table.get(gtx);
if (entry == null)
{
throw new Exception("entry for transaction " + gtx + " not found in transaction table");
}
modifications = entry.getCacheLoaderModifications();
if (modifications.size() == 0)
{
return;
}
List cache_loader_modifications = new ArrayList();
for (MethodCall methodCall : modifications)
{
Method method = methodCall.getMethod();
Object[] args;
if (method == null)
{
throw new Exception("method call has no method: " + methodCall);
}
args = methodCall.getArgs();
switch (methodCall.getMethodId())
{
case MethodDeclarations.removeNodeMethodLocal_id:
// just remove it from loader, don't trigger activation processing
Modification mod = new Modification(Modification.ModificationType.REMOVE_NODE, (Fqn) args[1]);
cache_loader_modifications.add(mod);
break;
case MethodDeclarations.putDataMethodLocal_id:
case MethodDeclarations.putDataEraseMethodLocal_id:
case MethodDeclarations.putKeyValMethodLocal_id:
// On the way out, remove the node from the cache loader.
// Only remove the node if it exists in memory, its attributes have
// been initialized, its children have been loaded
// AND it was found in the cache loader (nodeLoaded = true).
// Then notify the listeners that the node has been activated.
Fqn fqn = (Fqn) args[1];
if (fqn != null && peekNode(ctx, fqn, false, false, false) != null && loader.exists(fqn))
{
NodeSPI n = peekNode(ctx, fqn, false, true, false);// don't load
// node not null and attributes have been loaded?
if (n != null && n.isDataLoaded())
{
// has children?
if (!n.getChildrenDirect().isEmpty() && allInitialized(n))
{
// children have been loaded, remove the node
addRemoveMod(ctx, cache_loader_modifications, fqn, n.getDataDirect());
txActs++;
}
// doesn't have children, check the cache loader
else if (loaderNoChildren(fqn))
{
addRemoveMod(ctx, cache_loader_modifications, fqn, n.getDataDirect());
txActs++;
}
}
}
break;
}
}
if (cache_loader_modifications.size() > 0)
{
loader.prepare(gtx, cache_loader_modifications, false);
if (configuration.getExposeManagementStatistics() && getStatisticsEnabled() && txActs > 0)
{
m_txActivations.put(gtx, txActs);
}
}
}
private void addRemoveMod(InvocationContext ctx, List l, Fqn fqn, Map data)
{
Modification mod = new Modification(Modification.ModificationType.REMOVE_NODE, fqn);
l.add(mod);
cache.getNotifier().notifyNodeActivated(fqn, false, data, ctx);
}
}