package org.infinispan.interceptors.impl;
import static org.infinispan.persistence.manager.PersistenceManager.AccessMode.SHARED;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.transaction.Transaction;
import org.infinispan.atomic.impl.AtomicHashMap;
import org.infinispan.commands.AbstractVisitor;
import org.infinispan.commands.tx.CommitCommand;
import org.infinispan.commands.tx.PrepareCommand;
import org.infinispan.commands.tx.RollbackCommand;
import org.infinispan.commands.write.ApplyDeltaCommand;
import org.infinispan.commands.write.ClearCommand;
import org.infinispan.commands.write.PutKeyValueCommand;
import org.infinispan.commands.write.PutMapCommand;
import org.infinispan.commands.write.RemoveCommand;
import org.infinispan.commands.write.ReplaceCommand;
import org.infinispan.commands.write.WriteCommand;
import org.infinispan.commons.marshall.StreamingMarshaller;
import org.infinispan.container.InternalEntryFactory;
import org.infinispan.container.entries.CacheEntry;
import org.infinispan.container.entries.DeltaAwareCacheEntry;
import org.infinispan.container.entries.InternalCacheEntry;
import org.infinispan.container.entries.InternalCacheValue;
import org.infinispan.context.InvocationContext;
import org.infinispan.context.impl.TxInvocationContext;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.interceptors.DDAsyncInterceptor;
import org.infinispan.marshall.core.MarshalledEntryImpl;
import org.infinispan.persistence.PersistenceUtil;
import org.infinispan.persistence.manager.PersistenceManager;
import org.infinispan.persistence.support.BatchModification;
/**
* An interceptor which ensures that writes to an underlying transactional store are prepared->committed/rolledback as part
* of the 2PC, therefore ensuring that the cache and transactional store(s) remain consistent.
*
* @author Ryan Emerson
* @since 9.0
*/
public class TransactionalStoreInterceptor extends DDAsyncInterceptor {
private PersistenceManager persistenceManager;
private InternalEntryFactory entryFactory;
private StreamingMarshaller marshaller;
@Inject
protected void init(PersistenceManager persistenceManager, InternalEntryFactory entryFactory,
StreamingMarshaller marshaller) {
this.persistenceManager = persistenceManager;
this.entryFactory = entryFactory;
this.marshaller = marshaller;
}
@Override
public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command) throws Throwable {
if (ctx.isOriginLocal()) {
Transaction tx = ctx.getTransaction();
Updater modBuilder = new Updater(ctx.getCacheTransaction().getAffectedKeys());
List<WriteCommand> modifications = ctx.getCacheTransaction().getAllModifications();
for (WriteCommand writeCommand : modifications) {
writeCommand.acceptVisitor(ctx, modBuilder);
}
persistenceManager.prepareAllTxStores(tx, modBuilder.modifications, SHARED);
}
return invokeNext(ctx, command);
}
@Override
public Object visitCommitCommand(TxInvocationContext ctx, CommitCommand command) throws Throwable {
if (ctx.isOriginLocal()) {
persistenceManager.commitAllTxStores(ctx.getTransaction(), SHARED);
}
return invokeNext(ctx, command);
}
@Override
public Object visitRollbackCommand(TxInvocationContext ctx, RollbackCommand command) throws Throwable {
if (ctx.isOriginLocal()) {
persistenceManager.rollbackAllTxStores(ctx.getTransaction(), SHARED);
}
return invokeNext(ctx, command);
}
private class Updater extends AbstractVisitor {
private final BatchModification modifications;
Updater(Set<Object> affectedKeys) {
modifications = new BatchModification(affectedKeys);
}
@Override
public Object visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command) throws Throwable {
return visitSingleStore(ctx, command.getKey());
}
@Override
public Object visitApplyDeltaCommand(InvocationContext ctx, ApplyDeltaCommand command) throws Throwable {
CacheEntry entry = ctx.lookupEntry(command.getKey());
InternalCacheEntry ice;
if (entry instanceof InternalCacheEntry) {
ice = (InternalCacheEntry) entry;
} else if (entry instanceof DeltaAwareCacheEntry) {
AtomicHashMap<?, ?> uncommittedChanges = ((DeltaAwareCacheEntry) entry).getUncommittedChages();
ice = entryFactory.create(entry.getKey(), uncommittedChanges, entry.getMetadata(), entry.getLifespan(), entry.getMaxIdle());
} else {
ice = entryFactory.create(entry);
}
MarshalledEntryImpl marshalledEntry = new MarshalledEntryImpl(ice.getKey(), ice.getValue(), PersistenceUtil.internalMetadata(ice), marshaller);
modifications.addMarshalledEntry(ice.getKey(), marshalledEntry);
return null;
}
@Override
public Object visitReplaceCommand(InvocationContext ctx, ReplaceCommand command) throws Throwable {
return visitSingleStore(ctx, command.getKey());
}
@Override
public Object visitPutMapCommand(InvocationContext ctx, PutMapCommand command) throws Throwable {
Map<Object, Object> map = command.getMap();
for (Object key : map.keySet())
visitSingleStore(ctx, key);
return null;
}
@Override
public Object visitRemoveCommand(InvocationContext ctx, RemoveCommand command) throws Throwable {
modifications.removeEntry(command.getKey());
return null;
}
@Override
public Object visitClearCommand(InvocationContext ctx, ClearCommand command) throws Throwable {
// This should never happen as the ClearCommand should never be included in a Tx's modification list
throw new UnsupportedOperationException("Clear command not supported inside a transaction");
}
private Object visitSingleStore(InvocationContext ctx, Object key) throws Throwable {
InternalCacheValue icv = entryFactory.getValueFromCtxOrCreateNew(key, ctx);
modifications.addMarshalledEntry(key, new MarshalledEntryImpl(key, icv.getValue(), PersistenceUtil.internalMetadata(icv), marshaller));
return null;
}
}
}