/*
* JBoss, the OpenSource J2EE webOS
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.cache.statetransfer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.CacheException;
import org.jboss.cache.CacheSPI;
import org.jboss.cache.Fqn;
import org.jboss.cache.NodeSPI;
import org.jboss.cache.RegionEmptyException;
import org.jboss.cache.RegionManager;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.factories.annotations.Inject;
import org.jboss.cache.factories.annotations.NonVolatile;
import org.jboss.cache.loader.CacheLoaderManager;
import org.jboss.cache.lock.LockManager;
import static org.jboss.cache.lock.LockType.READ;
import org.jboss.cache.lock.TimeoutException;
import org.jboss.cache.marshall.InactiveRegionException;
import org.jboss.cache.marshall.Marshaller;
import org.jboss.cache.marshall.NodeData;
import org.jboss.cache.marshall.NodeDataMarker;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
@NonVolatile
public class StateTransferManager
{
protected final static Log log = LogFactory.getLog(StateTransferManager.class);
public static final NodeData STREAMING_DELIMITER_NODE = new NodeDataMarker();
public static final String PARTIAL_STATE_DELIMITER = "_PARTIAL_STATE_DELIMITER";
private CacheSPI cache;
private Marshaller marshaller;
private RegionManager regionManager;
private Configuration configuration;
private LockManager lockManager;
public StateTransferManager()
{
}
@Inject
public void injectDependencies(CacheSPI cache, Marshaller marshaller, RegionManager regionManager, Configuration configuration, LockManager lockManager)
{
this.cache = cache;
this.regionManager = regionManager;
this.marshaller = marshaller;
this.configuration = configuration;
this.lockManager = lockManager;
}
public StateTransferManager(CacheSPI cache)
{
this.cache = cache;
}
/**
* Writes the state for the portion of the tree named by <code>fqn</code> to
* the provided OutputStream.
* <p/>
* <p/>
*
* @param out stream to write state to
* @param fqn Fqn indicating the uppermost node in the
* portion of the tree whose state should be returned.
* @param timeout max number of ms this method should wait to acquire
* a read lock on the nodes being transferred
* @param force if a read lock cannot be acquired after
* <code>timeout</code> ms, should the lock acquisition
* be forced, and any existing transactions holding locks
* on the nodes be rolled back? <strong>NOTE:</strong>
* In release 1.2.4, this parameter has no effect.
* @param suppressErrors should any Throwable thrown be suppressed?
* @throws Throwable in event of error
*/
public void getState(ObjectOutputStream out, Fqn fqn, long timeout, boolean force, boolean suppressErrors) throws Throwable
{
// can't give state for regions currently being activated/inactivated
boolean canProvideState = (!regionManager.isInactive(fqn) && cache.peek(fqn, false) != null);
boolean fetchTransientState = configuration.isFetchInMemoryState();
CacheLoaderManager cacheLoaderManager = cache.getCacheLoaderManager();
boolean fetchPersistentState = cacheLoaderManager != null && cacheLoaderManager.isFetchPersistentState();
if (canProvideState && (fetchPersistentState || fetchTransientState))
{
marshaller.objectToObjectStream(true, out);
StateTransferGenerator generator = getStateTransferGenerator();
Object owner = getOwnerForLock();
long startTime = System.currentTimeMillis();
NodeSPI rootNode = cache.peek(fqn, false, false);
try
{
if (log.isDebugEnabled())
{
log.debug("locking the " + fqn + " subtree to return the in-memory (transient) state");
}
acquireLocksForStateTransfer(rootNode, owner, timeout, true, force);
generator.generateState(out, rootNode, fetchTransientState, fetchPersistentState, suppressErrors);
if (log.isDebugEnabled())
{
log.debug("Successfully generated state in " + (System.currentTimeMillis() - startTime) + " msec");
}
}
finally
{
releaseStateTransferLocks(rootNode, owner, true);
}
}
else
{
marshaller.objectToObjectStream(false, out);
Exception e = null;
if (!canProvideState)
{
String exceptionMessage = "Cache instance at " + cache.getLocalAddress() + " cannot provide state for fqn " + fqn + ".";
if (regionManager.isInactive(fqn))
{
exceptionMessage += " Region for fqn " + fqn + " is inactive.";
e = new InactiveRegionException(exceptionMessage);
}
// this is not really an exception. Just provide empty state. The exception is just a signal. Yes, lousy. - JBCACHE-1349
if (cache.peek(fqn, false, false) == null)
{
e = new RegionEmptyException();
}
}
if (!fetchPersistentState && !fetchTransientState)
{
e = new CacheException("Cache instance at " + cache.getLocalAddress() + " is not configured to provide state");
}
marshaller.objectToObjectStream(e, out);
if (e != null) throw e;
}
}
/**
* Set the portion of the cache rooted in <code>targetRoot</code>
* to match the given state. Updates the contents of <code>targetRoot</code>
* to reflect those in <code>new_state</code>.
* <p/>
* <strong>NOTE:</strong> This method performs no locking of nodes; it
* is up to the caller to lock <code>targetRoot</code> before calling
* this method.
* <p/>
* This method will use any {@link ClassLoader} needed as defined by the active {@link org.jboss.cache.Region}
* in the {@link org.jboss.cache.RegionManager}, pertaining to the targetRoot passed in.
*
* @param in an input stream containing the state
* @param targetRoot fqn of the node into which the state should be integrated
* @throws Exception In event of error
*/
public void setState(ObjectInputStream in, Fqn targetRoot) throws Exception
{
NodeSPI target = cache.peek(targetRoot, false, false);
if (target == null)
{
// Create the integration root, but do not replicate
cache.getInvocationContext().getOptionOverrides().setCacheModeLocal(true);
//needed for BR state transfers
cache.getInvocationContext().getOptionOverrides().setSkipCacheStatusCheck(true);
cache.put(targetRoot, null);
target = cache.peek(targetRoot, false, false);
}
Object o = marshaller.objectFromObjectStream(in);
Boolean hasState = (Boolean) o;
if (hasState)
{
setState(in, target);
}
else
{
throw new CacheException("Cache instance at " + cache.getLocalAddress()
+ " cannot integrate state since state provider could not provide state due to " + marshaller.objectFromObjectStream(in));
}
}
/**
* Set the portion of the cache rooted in <code>targetRoot</code>
* to match the given state. Updates the contents of <code>targetRoot</code>
* to reflect those in <code>new_state</code>.
* <p/>
* <strong>NOTE:</strong> This method performs no locking of nodes; it
* is up to the caller to lock <code>targetRoot</code> before calling
* this method.
*
* @param state a serialized byte[][] array where element 0 is the
* transient state (or null) , and element 1 is the
* persistent state (or null)
* @param targetRoot node into which the state should be integrated
*/
private void setState(ObjectInputStream state, NodeSPI targetRoot) throws Exception
{
Object owner = getOwnerForLock();
long timeout = configuration.getStateRetrievalTimeout();
long startTime = System.currentTimeMillis();
try
{
// Acquire a lock on the root node
acquireLocksForStateTransfer(targetRoot, owner, timeout, true, true);
/*
* Vladimir/Manik/Brian (Dec 7,2006)
*
* integrator.integrateState(in,targetRoot, cl) will call cache.put for each
* node read from stream. Having option override below allows nodes read
* to be directly stored into a tree since we bypass interceptor chain.
*
*/
// Option option = new Option();
// option.setBypassInterceptorChain(true);
// cache.getInvocationContext().setOptionOverrides(option);
//
StateTransferIntegrator integrator = getStateTransferIntegrator(state, targetRoot.getFqn());
if (log.isDebugEnabled())
{
log.debug("starting state integration at node " + targetRoot);
}
integrator.integrateState(state, targetRoot);
if (log.isDebugEnabled())
{
log.debug("successfully integrated state in " + (System.currentTimeMillis() - startTime) + " msec");
}
}
finally
{
releaseStateTransferLocks(targetRoot, owner, true);
}
}
/**
* Acquires locks on a root node for an owner for state transfer.
*/
protected void acquireLocksForStateTransfer(NodeSPI root,
Object lockOwner,
long timeout,
boolean lockChildren,
boolean force)
throws Exception
{
try
{
if (lockChildren)
{
lockManager.lockAll(root, READ, lockOwner, timeout, true);
}
else
{
lockManager.lock(Fqn.ROOT, READ, lockOwner, timeout);
}
}
catch (TimeoutException te)
{
log.error("Caught TimeoutException acquiring locks on region " +
root.getFqn(), te);
if (force)
{
// Until we have FLUSH in place, don't force locks
// forceAcquireLock(root, lockOwner, lockChildren);
throw te;
}
else
{
throw te;
}
}
}
/**
* Releases all state transfer locks acquired.
*
* @see #acquireLocksForStateTransfer
*/
protected void releaseStateTransferLocks(NodeSPI root,
Object lockOwner,
boolean childrenLocked)
{
try
{
if (childrenLocked)
{
lockManager.unlockAll(root, lockOwner);
}
else
{
lockManager.unlock(Fqn.ROOT, lockOwner);
}
}
catch (Throwable t)
{
log.error("failed releasing locks", t);
}
}
protected StateTransferGenerator getStateTransferGenerator()
{
return StateTransferFactory.getStateTransferGenerator(cache);
}
protected StateTransferIntegrator getStateTransferIntegrator(ObjectInputStream istream, Fqn fqn) throws Exception
{
return StateTransferFactory.getStateTransferIntegrator(istream, fqn, cache);
}
/**
* Returns an object suitable for use in node locking, either the current
* transaction or the current thread if there is no transaction.
*/
private Object getOwnerForLock()
{
Object owner = cache.getCurrentTransaction();
if (owner == null)
{
owner = Thread.currentThread();
}
return owner;
}
}