package org.jboss.cache.interceptors;
import org.jboss.cache.InvocationContext;
import org.jboss.cache.commands.AbstractVisitor;
import org.jboss.cache.commands.VersionedDataCommand;
import org.jboss.cache.commands.VisitableCommand;
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.PutDataMapCommand;
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.config.Option;
import org.jboss.cache.transaction.GlobalTransaction;
import org.jboss.cache.transaction.OptimisticTransactionEntry;
import org.jboss.cache.transaction.TransactionEntry;
import javax.transaction.Transaction;
import java.util.List;
/**
* A new interceptor to simplify functionality in the {@link org.jboss.cache.interceptors.TxInterceptor}.
*
* @author Manik Surtani (<a href="mailto:manik@jboss.org">manik@jboss.org</a>)
* @since 2.2.0
*/
public class OptimisticTxInterceptor extends TxInterceptor
{
protected final ModificationsReplayVisitor replayVisitor = new ModificationsReplayVisitor();
public OptimisticTxInterceptor()
{
optimistic = true;
}
@Override
public Object visitOptimisticPrepareCommand(InvocationContext ctx, OptimisticPrepareCommand command) throws Throwable
{
// nothing really different from a pessimistic prepare command.
return visitPrepareCommand(ctx, command);
}
@Override
public Object handleDefault(InvocationContext ctx, VisitableCommand command) throws Throwable
{
try
{
Transaction tx = ctx.getTransaction();
boolean implicitTransaction = tx == null;
if (implicitTransaction)
{
tx = createLocalTx();
// we need to attach this tx to the InvocationContext.
ctx.setTransaction(tx);
}
try
{
Object retval = attachGtxAndPassUpChain(ctx, command);
if (implicitTransaction)
{
copyInvocationScopeOptionsToTxScope(ctx);
copyForcedCacheModeToTxScope(ctx);
txManager.commit();
}
return retval;
}
catch (Throwable t)
{
if (implicitTransaction)
{
log.warn("Rolling back, exception encountered", t);
try
{
copyInvocationScopeOptionsToTxScope(ctx);
copyForcedCacheModeToTxScope(ctx);
txManager.rollback();
}
catch (Throwable th)
{
log.warn("Roll back failed encountered", th);
}
throw t;
}
}
}
catch (Throwable th)
{
ctx.throwIfNeeded(th);
}
return null;
}
private void copyForcedCacheModeToTxScope(InvocationContext ctx)
{
Option optionOverride = ctx.getOptionOverrides();
if (optionOverride != null
&& (optionOverride.isForceAsynchronous() || optionOverride.isForceSynchronous()))
{
TransactionEntry entry = ctx.getTransactionEntry();
if (entry != null)
{
if (optionOverride.isForceAsynchronous())
entry.setForceAsyncReplication(true);
else
entry.setForceSyncReplication(true);
}
}
}
@Override
protected PrepareCommand buildPrepareCommand(GlobalTransaction gtx, List modifications, boolean onePhaseCommit)
{
// optimistic locking NEVER does one-phase prepares.
return commandsFactory.buildOptimisticPrepareCommand(gtx, modifications, null, rpcManager.getLocalAddress(), false);
}
/**
* Replays modifications by passing them up the interceptor chain.
*
* @throws Throwable
*/
@Override
protected void replayModifications(InvocationContext ctx, Transaction ltx, PrepareCommand command) throws Throwable
{
if (log.isDebugEnabled()) log.debug("Handling optimistic remote prepare " + ctx.getGlobalTransaction());
// invoke all modifications by passing them up the chain, setting data versions first.
try
{
replayVisitor.visitCollection(ctx, command.getModifications());
}
catch (Throwable t)
{
log.error("Prepare failed!", t);
throw t;
}
}
@Override
protected void cleanupStaleLocks(InvocationContext ctx) throws Throwable
{
super.cleanupStaleLocks(ctx);
TransactionEntry entry = ctx.getTransactionEntry();
if (entry != null)
{
((OptimisticTransactionEntry) entry).getTransactionWorkSpace().clearNodes();
}
}
@Override
protected TransactionEntry createNewTransactionEntry(Transaction tx) throws Exception
{
return new OptimisticTransactionEntry(tx);
}
private class ModificationsReplayVisitor extends AbstractVisitor
{
@Override
public Object handleDefault(InvocationContext ctx, VisitableCommand command) throws Throwable
{
Object result = invokeNextInterceptor(ctx, command);
assertTxIsStillValid(ctx.getTransaction());
return result;
}
@Override
public Object visitPutDataMapCommand(InvocationContext ctx, PutDataMapCommand command) throws Throwable
{
return handleDataVersionCommand(ctx, command);
}
@Override
public Object visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command) throws Throwable
{
return handleDataVersionCommand(ctx, command);
}
@Override
public Object visitRemoveNodeCommand(InvocationContext ctx, RemoveNodeCommand command) throws Throwable
{
return handleDataVersionCommand(ctx, command);
}
@Override
public Object visitClearDataCommand(InvocationContext ctx, ClearDataCommand command) throws Throwable
{
return handleDataVersionCommand(ctx, command);
}
@Override
public Object visitRemoveKeyCommand(InvocationContext ctx, RemoveKeyCommand command) throws Throwable
{
return handleDataVersionCommand(ctx, command);
}
private Object handleDataVersionCommand(InvocationContext ctx, VersionedDataCommand command) throws Throwable
{
Option originalOption = ctx.getOptionOverrides();
if (command.isVersioned())
{
Option option = new Option();
option.setDataVersion(command.getDataVersion());
ctx.setOptionOverrides(option);
}
Object retval;
try
{
retval = invokeNextInterceptor(ctx, command);
assertTxIsStillValid(ctx.getTransaction());
}
catch (Throwable t)
{
log.error("method invocation failed", t);
throw t;
}
finally
{
ctx.setOptionOverrides(originalOption);
}
return retval;
}
}
}