/*
* JBoss, Home of Professional Open Source.
* Copyright 2000 - 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.cache.interceptors;
import org.jboss.cache.Fqn;
import org.jboss.cache.InternalNode;
import org.jboss.cache.InvocationContext;
import org.jboss.cache.Modification;
import org.jboss.cache.commands.AbstractVisitor;
import org.jboss.cache.commands.read.GetChildrenNamesCommand;
import org.jboss.cache.commands.read.GetKeyValueCommand;
import org.jboss.cache.commands.read.GetKeysCommand;
import org.jboss.cache.commands.read.GetNodeCommand;
import org.jboss.cache.commands.tx.OptimisticPrepareCommand;
import org.jboss.cache.commands.tx.PrepareCommand;
import org.jboss.cache.commands.write.ClearDataCommand;
import org.jboss.cache.commands.write.MoveCommand;
import org.jboss.cache.commands.write.PutDataMapCommand;
import org.jboss.cache.commands.write.PutForExternalReadCommand;
import org.jboss.cache.commands.write.PutKeyValueCommand;
import org.jboss.cache.commands.write.RemoveKeyCommand;
import org.jboss.cache.commands.write.RemoveNodeCommand;
import org.jboss.cache.factories.annotations.Inject;
import org.jboss.cache.factories.annotations.Start;
import org.jboss.cache.jmx.annotations.ManagedAttribute;
import org.jboss.cache.jmx.annotations.ManagedOperation;
import org.jboss.cache.transaction.GlobalTransaction;
import org.jboss.cache.transaction.TransactionContext;
import javax.transaction.SystemException;
import javax.transaction.TransactionManager;
import java.util.ArrayList;
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 6776 2008-09-22 17:35:46Z manik.surtani@jboss.com $
*/
public class ActivationInterceptor extends CacheLoaderInterceptor
{
protected TransactionManager txMgr = null;
private long activations = 0;
ActivationModificationsBuilder builder;
/**
* List<Transaction> that we have registered for
*/
protected ConcurrentHashMap transactions = new ConcurrentHashMap(16);
protected static final Object NULL = new Object();
public ActivationInterceptor()
{
isActivation = true;
useCacheStore = false;
}
@Inject
public void injectTransactionManager(TransactionManager txMgr)
{
this.txMgr = txMgr;
}
@Start
public void createModificationsBuilder()
{
builder = new ActivationModificationsBuilder();
}
@Override
public Object visitClearDataCommand(InvocationContext ctx, ClearDataCommand command) throws Throwable
{
Object returnValue = super.visitClearDataCommand(ctx, command);
if (trace)
log.trace("This is a remove data operation; removing the data from the loader, no activation processing needed.");
loader.removeData(command.getFqn());
return returnValue;
}
@Override
public Object visitRemoveNodeCommand(InvocationContext ctx, RemoveNodeCommand command) throws Throwable
{
Object returnValue = super.visitRemoveNodeCommand(ctx, command);
if (trace)
log.trace("This is a remove operation; removing the node from the loader, no activation processing needed.");
loader.remove(command.getFqn());
return returnValue;
}
@Override
public Object visitGetChildrenNamesCommand(InvocationContext ctx, GetChildrenNamesCommand command) throws Throwable
{
Object returnValue = super.visitGetChildrenNamesCommand(ctx, command);
removeNodeFromCacheLoader(command.getFqn());
return returnValue;
}
@Override
public Object visitGetKeysCommand(InvocationContext ctx, GetKeysCommand command) throws Throwable
{
Object returnValue = super.visitGetKeysCommand(ctx, command);
removeNodeFromCacheLoader(command.getFqn());
return returnValue;
}
@Override
public Object visitGetNodeCommand(InvocationContext ctx, GetNodeCommand command) throws Throwable
{
Object returnValue = super.visitGetNodeCommand(ctx, command);
removeNodeFromCacheLoader(command.getFqn());
return returnValue;
}
@Override
public Object visitGetKeyValueCommand(InvocationContext ctx, GetKeyValueCommand command) throws Throwable
{
Object returnValue = super.visitGetKeyValueCommand(ctx, command);
removeNodeFromCacheLoader(command.getFqn());
return returnValue;
}
@Override
public Object visitPutForExternalReadCommand(InvocationContext ctx, PutForExternalReadCommand command) throws Throwable
{
return visitPutKeyValueCommand(ctx, command);
}
@Override
public Object visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command) throws Throwable
{
Object returnValue = super.visitPutKeyValueCommand(ctx, command);
removeNodeFromCacheLoader(command.getFqn());
return returnValue;
}
@Override
public Object visitPutDataMapCommand(InvocationContext ctx, PutDataMapCommand command) throws Throwable
{
Object returnValue = super.visitPutDataMapCommand(ctx, command);
removeNodeFromCacheLoader(command.getFqn());
return returnValue;
}
@Override
public Object visitRemoveKeyCommand(InvocationContext ctx, RemoveKeyCommand command) throws Throwable
{
Object returnValue = super.visitRemoveKeyCommand(ctx, command);
removeNodeFromCacheLoader(command.getFqn());
return returnValue;
}
@Override
public Object visitMoveCommand(InvocationContext ctx, MoveCommand command) throws Throwable
{
Object returnValue = super.visitMoveCommand(ctx, command);
if (trace)
log.trace("This is a move operation; removing the FROM node from the loader, no activation processing needed.");
loader.remove(command.getFqn());
removeNodeFromCacheLoader(command.getFqn().getParent());
removeNodeFromCacheLoader(command.getTo());
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(Fqn fqn) throws Throwable
{
if (fqn == null) return;
InternalNode n;
if (((n = dataContainer.peekInternalNode(fqn, true)) != null) && n.isDataLoaded() && loader.exists(fqn))
{
// node not null and attributes have been loaded?
if (n.hasChildren())
{
boolean result = childrenLoaded(n);
if (result)
{
log.debug("children all initialized");
remove(fqn);
}
}
else if (loaderNoChildren(fqn))
{
if (log.isDebugEnabled()) log.debug("no children " + n);
remove(fqn);
}
}
}
private boolean childrenLoaded(InternalNode<?, ?> node)
{
if (!node.isChildrenLoaded())
{
return false;
}
for (InternalNode child : node.getChildren())
{
if (!child.isDataLoaded())
{
return false;
}
if (child.hasChildren())
{
if (!childrenLoaded(child))
return false;
}
else if (!loaderNoChildren(child.getFqn()))
{
return false;
}
}
return true;
}
@Override
public Object visitOptimisticPrepareCommand(InvocationContext ctx, OptimisticPrepareCommand command) throws Throwable
{
Object retval = invokeNextInterceptor(ctx, command);
if (inTransaction())
{
prepareCacheLoader(ctx);
}
return retval;
}
private boolean inTransaction() throws SystemException
{
return txMgr != null && txMgr.getTransaction() != null;
}
@Override
public Object visitPrepareCommand(InvocationContext ctx, PrepareCommand command) throws Throwable
{
Object retval = invokeNextInterceptor(ctx, command);
if (inTransaction())
{
prepareCacheLoader(ctx);
}
return retval;
}
private void remove(Fqn fqn) throws Exception
{
loader.remove(fqn);
if (getStatisticsEnabled()) activations++;
}
/**
* Returns true if the loader indicates no children for this node.
* Return false on error.
*/
private boolean loaderNoChildren(Fqn fqn)
{
try
{
Set childrenNames = loader.getChildrenNames(fqn);
return (childrenNames == null);
}
catch (Exception e)
{
log.error("failed getting the children names for " + fqn + " from the cache loader", e);
return false;
}
}
private void prepareCacheLoader(InvocationContext ctx) throws Throwable
{
GlobalTransaction gtx = ctx.getGlobalTransaction();
TransactionContext tCtx = ctx.getTransactionContext();
if (tCtx == null)
{
throw new Exception("tCtx for transaction " + gtx + " not found in transaction table");
}
List<Modification> cacheLoaderModifications = new ArrayList<Modification>();
builder.visitCollection(ctx, tCtx.getModifications());
if (cacheLoaderModifications.size() > 0)
{
loader.prepare(gtx, cacheLoaderModifications, false);
}
}
public class ActivationModificationsBuilder extends AbstractVisitor
{
private List<Modification> cacheLoaderModifications = new ArrayList<Modification>();
private int txActs = 0;
@Override
public Object visitRemoveNodeCommand(InvocationContext ctx, RemoveNodeCommand removeNodeCommand) throws Throwable
{
Modification mod = new Modification(Modification.ModificationType.REMOVE_NODE, removeNodeCommand.getFqn());
cacheLoaderModifications.add(mod);
return null;
}
@Override
public Object visitPutDataMapCommand(InvocationContext ctx, PutDataMapCommand command) throws Throwable
{
Fqn fqn = command.getFqn();
handlePutCommand(ctx, fqn);
return null;
}
@Override
public Object visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command) throws Throwable
{
Fqn fqn = command.getFqn();
handlePutCommand(ctx, fqn);
return null;
}
// 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.
private void handlePutCommand(InvocationContext ctx, Fqn fqn)
throws Exception
{
if (fqn != null && dataContainer.exists(fqn) && loader.exists(fqn))
{
InternalNode n = dataContainer.peekInternalNode(fqn, true);// don't load
// node not null and attributes have been loaded?
if (n != null && n.isDataLoaded())
{
// has children?
boolean result = childrenLoaded(n);
if (n.hasChildren() && result)
{
// children have been loaded, remove the node
addRemoveMod(ctx, cacheLoaderModifications, fqn, n.getData());
txActs++;
}
// doesn't have children, check the cache loader
else if (loaderNoChildren(fqn))
{
addRemoveMod(ctx, cacheLoaderModifications, fqn, n.getData());
txActs++;
}
}
}
}
private boolean loaderNoChildren(Fqn fqn) throws Exception
{
return loader.getChildrenNames(fqn) != null;
}
private void addRemoveMod(InvocationContext ctx, List<Modification> l, Fqn fqn, Map data)
{
Modification mod = new Modification(Modification.ModificationType.REMOVE_NODE, fqn);
l.add(mod);
notifier.notifyNodeActivated(fqn, false, data, ctx);
}
public List<Modification> getCacheLoaderModifications()
{
return cacheLoaderModifications;
}
public int getTxActs()
{
return txActs;
}
}
@ManagedAttribute(description = "number of cache node activations")
public long getActivations()
{
return activations;
}
@ManagedOperation
public void resetStatistics()
{
super.resetStatistics();
activations = 0;
}
@ManagedOperation
public Map<String, Object> dumpStatistics()
{
Map<String, Object> retval = super.dumpStatistics();
if (retval == null)
{
retval = new HashMap<String, Object>();
}
retval.put("Activations", activations);
return retval;
}
}