package org.jboss.cache.interceptors;
import org.jboss.cache.CacheException;
import org.jboss.cache.Fqn;
import org.jboss.cache.InvocationContext;
import org.jboss.cache.Modification;
import org.jboss.cache.NodeSPI;
import org.jboss.cache.config.CacheLoaderConfig;
import org.jboss.cache.factories.annotations.Start;
import org.jboss.cache.loader.CacheLoader;
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.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Writes modifications back to the store on the way out: stores modifications back
* through the CacheLoader, either after each method call (no TXs), or at TX commit.
*
* @author Bela Ban
* @version $Id: CacheStoreInterceptor.java 5413 2008-03-11 21:59:36Z mircea.markus $
*/
public class CacheStoreInterceptor extends MethodDispacherInterceptor implements CacheStoreInterceptorMBean
{
protected CacheLoaderConfig loaderConfig = null;
protected TransactionManager tx_mgr = null;
protected TransactionTable tx_table = null;
private HashMap m_txStores = new HashMap();
private Map<GlobalTransaction, Set<Fqn>> preparingTxs = new ConcurrentHashMap<GlobalTransaction, Set<Fqn>>();
private long m_cacheStores = 0;
protected CacheLoader loader;
public CacheStoreInterceptor()
{
initLogger();
}
@Start
protected void startInterceptor()
{
loader = cache.getCacheLoaderManager().getCacheLoader();
this.loaderConfig = cache.getCacheLoaderManager().getCacheLoaderConfig();
tx_mgr = cache.getTransactionManager();
tx_table = cache.getTransactionTable();
}
/**
* if this is a shared cache loader and the call is of remote origin, pass up the chain
*/
@Override
protected boolean skipMethodCall(InvocationContext ctx)
{
if (!ctx.isOriginLocal() && loaderConfig.isShared())
{
if (trace)
{
log.trace("Passing up method call and bypassing this interceptor since the cache loader is shared and this call originated remotely.");
}
return true;
}
return false;
}
@Override
protected Object handleCommitMethod(InvocationContext ctx, GlobalTransaction gtx) throws Throwable
{
if (inTransaction())
{
if (trace) log.trace("transactional so don't put stuff in the cloader yet.");
if (ctx.isCacheLoaderHasMods())
{
// this is a commit call.
if (trace) log.trace("Calling loader.commit() for gtx " + gtx);
// sync call (a write) on the loader
// ignore modified FQNs
// List fqnsModified = getFqnsFromModificationList(tx_table.get(gtx).getCacheLoaderModifications());
try
{
loader.commit(gtx);
}
catch (Throwable t)
{
preparingTxs.remove(gtx);
throw t;
}
if (configuration.getExposeManagementStatistics() && getStatisticsEnabled())
{
Integer puts = (Integer) m_txStores.get(gtx);
if (puts != null)
{
m_cacheStores = m_cacheStores + puts;
}
m_txStores.remove(gtx);
}
Object returnValue = nextInterceptor(ctx);
// persist additional internal state, if any, and then clean up internal resources
Set<Fqn> affectedFqns = preparingTxs.remove(gtx);
if (affectedFqns != null)
{
storeInternalState(ctx, affectedFqns);
}
return returnValue;
}
else
{
if (trace) log.trace("Commit called with no modifications; ignoring.");
}
}
return nextInterceptor(ctx);
}
@Override
protected Object handleRollbackMethod(InvocationContext ctx, GlobalTransaction gtx) throws Throwable
{
if (inTransaction())
{
if (trace) log.trace("transactional so don't put stuff in the cloader yet.");
if (ctx.isCacheLoaderHasMods())
{
// this is a rollback method
if (preparingTxs.containsKey(gtx))
{
preparingTxs.remove(gtx);
loader.rollback(gtx);
}
if (configuration.getExposeManagementStatistics() && getStatisticsEnabled())
{
m_txStores.remove(gtx);
}
}
else
{
if (trace) log.trace("Rollback called with no modifications; ignoring.");
}
}
return nextInterceptor(ctx);
}
@Override
protected Object handleOptimisticPrepareMethod(InvocationContext ctx, GlobalTransaction gtx, List modifications, Map data, Address address, boolean onePhaseCommit) throws Throwable
{
if (inTransaction())
{
if (trace) log.trace("transactional so don't put stuff in the cloader yet.");
prepareCacheLoader(gtx, ctx.getMethodCall().isOnePhaseCommitPrepareMehod());
}
return nextInterceptor(ctx);
}
@Override
protected Object handlePrepareMethod(InvocationContext ctx, GlobalTransaction gtx, List modification, Address coordinator, boolean onePhaseCommit) throws Throwable
{
if (inTransaction())
{
if (trace) log.trace("transactional so don't put stuff in the cloader yet.");
prepareCacheLoader(gtx, ctx.getMethodCall().isOnePhaseCommitPrepareMehod());
}
return nextInterceptor(ctx);
}
/**
* remove() methods need to be applied to the CacheLoader before passing up the call: a listener might
* access an element just removed, causing the CacheLoader to *load* the element before *removing* it.
*/
@Override
protected Object handleRemoveNodeMethod(InvocationContext ctx, GlobalTransaction tx, Fqn fqn, boolean createUndoOps) throws Throwable
{
if (!inTransaction())
{
loader.remove(fqn);
}
return nextInterceptor(ctx);
}
/**
* @see #handleRemoveNodeMethod(org.jboss.cache.InvocationContext, org.jboss.cache.transaction.GlobalTransaction, org.jboss.cache.Fqn, boolean)
*/
@Override
protected Object handleRemoveKeyMethod(InvocationContext ctx, GlobalTransaction tx, Fqn fqn, Object key, boolean createUndoOps) throws Throwable
{
if (!inTransaction())
{
Object returnValue = loader.remove(fqn, key);
nextInterceptor(ctx);
return returnValue;
}
return nextInterceptor(ctx);
}
/**
* @see #handleRemoveNodeMethod(org.jboss.cache.InvocationContext, org.jboss.cache.transaction.GlobalTransaction, org.jboss.cache.Fqn, boolean)
*/
@Override
protected Object handleRemoveDataMethod(InvocationContext ctx, GlobalTransaction tx, Fqn fqn, boolean createUndoOps) throws Throwable
{
if (!inTransaction())
{
loader.removeData(fqn);
// we need to mark this node as data loaded
NodeSPI n = peekNode(ctx, fqn, false, false, false);//cache.peek(fqn, false);
if (n != null)
{
n.setDataLoaded(true);
}
}
return nextInterceptor(ctx);
}
@Override
protected Object handleMoveMethod(InvocationContext ctx, Fqn from, Fqn to) throws Throwable
{
Object returnValue = nextInterceptor(ctx);
if (inTransaction())
{
return returnValue;
}
Fqn newNodeFqn = new Fqn(to, from.getLastElement());
recursiveMove(from, newNodeFqn);
loader.remove(from);
return returnValue;
}
@Override
protected Object handlePutDataEraseMethod(InvocationContext ctx, GlobalTransaction gt, Fqn fqn, Map newData, boolean createUndoOps, boolean eraseContents) throws Throwable
{
Object returnValue = nextInterceptor(ctx);
if (inTransaction())
{
return returnValue;
}
loader.removeData(fqn);
// if we are erasing all the data then consider this node loaded
NodeSPI n = peekNode(ctx, fqn, false, false, false);//cache.peek(fqn, false);
n.setDataLoaded(true);
return returnValue;
}
@Override
protected Object handlePutDataMethod(InvocationContext ctx, GlobalTransaction tx, Fqn fqn, Map data, boolean createUndoOps) throws Throwable
{
Object returnValue = nextInterceptor(ctx);
if (inTransaction())
{
return returnValue;
}
loader.put(fqn, data);
if (configuration.getExposeManagementStatistics() && getStatisticsEnabled())
{
m_cacheStores++;
}
return returnValue;
}
@Override
protected Object handlePutForExternalReadMethod(InvocationContext ctx, GlobalTransaction tx, Fqn fqn, Object key, Object value) throws Throwable
{
return handlePutKeyValue(ctx, fqn, key, value);
}
@Override
protected Object handlePutKeyValueMethod(InvocationContext ctx, GlobalTransaction gtx, Fqn fqn, Object key, Object value, boolean createUndoOps) throws Throwable
{
return handlePutKeyValue(ctx, fqn, key, value);
}
private Object handlePutKeyValue(InvocationContext ctx, Fqn fqn, Object key, Object value)
throws Throwable
{
Object returnValue = nextInterceptor(ctx);
if (inTransaction())
{
return returnValue;
}
returnValue = loader.put(fqn, key, value);
if (configuration.getExposeManagementStatistics() && getStatisticsEnabled())
{
m_cacheStores++;
}
return returnValue;
}
private boolean inTransaction() throws SystemException
{
return tx_mgr != null && tx_mgr.getTransaction() != null;
}
private void storeInternalState(InvocationContext ctx, Set<Fqn> affectedFqns) throws Exception
{
if (cache.getConfiguration().isNodeLockingOptimistic())
{
for (Fqn f : affectedFqns)
{
// NOT going to store tombstones!!
NodeSPI n = peekNode(ctx, f, false, false, false);
if (n != null)
{
Map internalState = n.getInternalState(true);
loader.put(f, internalState);
}
}
}
}
private void recursiveMove(Fqn fqn, Fqn newFqn) throws Exception
{
List fqns = new ArrayList();
fqns.add(fqn);
fqns.add(newFqn);
loader.put(newFqn, loader.get(fqn));
//recurse
Set childrenNames = loader.getChildrenNames(fqn);
if (childrenNames != null)
{
for (Object child : childrenNames)
{
recursiveMove(new Fqn(fqn, child), new Fqn(newFqn, child));
}
}
}
public long getCacheLoaderStores()
{
return m_cacheStores;
}
@Override
public void resetStatistics()
{
m_cacheStores = 0;
}
@Override
public Map<String, Object> dumpStatistics()
{
Map<String, Object> retval = new HashMap<String, Object>();
retval.put("CacheLoaderStores", m_cacheStores);
return retval;
}
private void prepareCacheLoader(GlobalTransaction gtx, boolean onePhase) throws Exception
{
List<MethodCall> modifications;
TransactionEntry entry;
int txPuts = 0;
entry = tx_table.get(gtx);
if (entry == null)
{
throw new Exception("entry for transaction " + gtx + " not found in transaction table");
}
Set<Fqn> affectedFqns = new HashSet<Fqn>();
modifications = entry.getCacheLoaderModifications();
if (modifications.size() == 0)
{
if (trace) log.trace("Transaction has not logged any modifications!");
return;
}
if (trace) log.trace("Cache loader modification list: " + modifications);
List cache_loader_modifications = new ArrayList();
for (MethodCall methodCall : modifications)
{
Modification mod = convertMethodCallToModification(methodCall, affectedFqns);
cache_loader_modifications.add(mod);
if (configuration.getExposeManagementStatistics() && getStatisticsEnabled())
{
if ((mod.getType() == Modification.ModificationType.PUT_DATA) ||
(mod.getType() == Modification.ModificationType.PUT_DATA_ERASE) ||
(mod.getType() == Modification.ModificationType.PUT_KEY_VALUE))
{
txPuts++;
}
}
}
if (trace)
{
log.trace("Converted method calls to cache loader modifications. List size: " + cache_loader_modifications.size());
}
if (cache_loader_modifications.size() > 0)
{
loader.prepare(gtx, cache_loader_modifications, onePhase);
preparingTxs.put(gtx, affectedFqns);
if (configuration.getExposeManagementStatistics() && getStatisticsEnabled() && txPuts > 0)
{
m_txStores.put(gtx, txPuts);
}
}
}
/**
* Converts a method call to a Modification, to be sent to a cache loader. In addition, adds any affected Fqns to the
* affectedFqns collection, which is then used by the calling code.
*/
private Modification convertMethodCallToModification(MethodCall methodCall, Set<Fqn> affectedFqns) throws Exception
{
if (trace) log.trace("Converting method call " + methodCall + " to modification.");
Method method = methodCall.getMethod();
Object[] args;
if (method == null)
{
throw new Exception("method call has no method: " + methodCall);
}
args = methodCall.getArgs();
Modification mod;
Fqn fqn = (Fqn) args[1];
switch (methodCall.getMethodId())
{
case MethodDeclarations.putDataMethodLocal_id:
mod = new Modification(Modification.ModificationType.PUT_DATA,
fqn, (Map) args[2]);// data
break;
case MethodDeclarations.putDataEraseMethodLocal_id:
mod = new Modification(Modification.ModificationType.PUT_DATA_ERASE,
fqn, (Map) args[2]);// data
break;
case MethodDeclarations.putKeyValMethodLocal_id:
mod = new Modification(Modification.ModificationType.PUT_KEY_VALUE,
fqn, args[2], args[3]);// key + value
break;
case MethodDeclarations.removeNodeMethodLocal_id:
mod = new Modification(Modification.ModificationType.REMOVE_NODE,
fqn);
break;
case MethodDeclarations.removeKeyMethodLocal_id:
mod = new Modification(Modification.ModificationType.REMOVE_KEY_VALUE,
fqn, args[2]);// key
break;
case MethodDeclarations.removeDataMethodLocal_id:
mod = new Modification(Modification.ModificationType.REMOVE_DATA,
fqn);
break;
case MethodDeclarations.moveMethodLocal_id:
Fqn moveFrom = (Fqn) args[0];
affectedFqns.add(moveFrom);
mod = new Modification(Modification.ModificationType.MOVE, moveFrom, fqn);
break;
default:
throw new CacheException("method call " + method.getName() + " cannot be converted to a modification");
}
affectedFqns.add(fqn);
if (trace) log.trace("Converted " + methodCall + " to Modification of type " + mod.getType());
return mod;
}
}