/* * Copyright 2011 Red Hat, Inc. and/or its affiliates. * * 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 library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA */ package org.infinispan.interceptors; import org.infinispan.commands.AbstractVisitor; import org.infinispan.commands.VisitableCommand; import org.infinispan.commands.read.GetKeyValueCommand; import org.infinispan.commands.tx.CommitCommand; import org.infinispan.commands.tx.PrepareCommand; import org.infinispan.commands.write.*; import org.infinispan.container.CommitContextEntries; import org.infinispan.container.DataContainer; import org.infinispan.container.EntryFactory; import org.infinispan.container.entries.InternalCacheEntry; import org.infinispan.container.entries.MVCCEntry; import org.infinispan.context.InvocationContext; import org.infinispan.context.impl.TxInvocationContext; import org.infinispan.factories.annotations.Inject; import org.infinispan.interceptors.base.CommandInterceptor; import org.infinispan.interceptors.locking.ClusteringDependentLogic; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; /** * Interceptor in charge with wrapping entries and add them in caller's context. * * @author Mircea Markus * @author Pedro Ruivo * @since 5.1 */ public class EntryWrappingInterceptor extends CommandInterceptor { protected EntryFactory entryFactory; protected DataContainer dataContainer; protected ClusteringDependentLogic cll; protected CommitContextEntries commitContextEntries; protected final EntryWrappingVisitor entryWrappingVisitor = new EntryWrappingVisitor(); private static final Log log = LogFactory.getLog(EntryWrappingInterceptor.class); @Override protected Log getLog() { return log; } @Inject public void init(EntryFactory entryFactory, DataContainer dataContainer, ClusteringDependentLogic cll, CommitContextEntries commitContextEntries) { this.entryFactory = entryFactory; this.dataContainer = dataContainer; this.cll = cll; this.commitContextEntries = commitContextEntries; } @Override public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command) throws Throwable { wrapEntriesForPrepare(ctx, command); Object result = invokeNextInterceptor(ctx, command); //new commit conditions for total order if (shouldCommitEntries(command, ctx)) { commitContextEntries.commitContextEntries(ctx); } return result; } @Override public Object visitCommitCommand(TxInvocationContext ctx, CommitCommand command) throws Throwable { try { return invokeNextInterceptor(ctx, command); } finally { commitContextEntries.commitContextEntries(ctx); } } @Override public Object visitGetKeyValueCommand(InvocationContext ctx, GetKeyValueCommand command) throws Throwable { try { entryFactory.wrapEntryForReading(ctx, command.getKey()); return invokeNextInterceptor(ctx, command); } finally { //needed because entries might be added in L1 if (!ctx.isInTxScope()) commitContextEntries.commitContextEntries(ctx); } } @Override public Object visitInvalidateCommand(InvocationContext ctx, InvalidateCommand command) throws Throwable { if (command.getKeys() != null) { for (Object key : command.getKeys()) entryFactory.wrapEntryForReplace(ctx, key); } return invokeNextAndApplyChanges(ctx, command); } @Override public Object visitClearCommand(InvocationContext ctx, ClearCommand command) throws Throwable { for (InternalCacheEntry entry : dataContainer.entrySet(null)) entryFactory.wrapEntryForClear(ctx, entry.getKey()); return invokeNextAndApplyChanges(ctx, command); } @Override public Object visitInvalidateL1Command(InvocationContext ctx, InvalidateL1Command command) throws Throwable { for (Object key : command.getKeys()) { entryFactory.wrapEntryForReplace(ctx, key); } return invokeNextAndApplyChanges(ctx, command); } @Override public Object visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command) throws Throwable { entryFactory.wrapEntryForPut(ctx, command.getKey(), null, !command.isPutIfAbsent()); return invokeNextAndApplyChanges(ctx, command); } @Override public Object visitApplyDeltaCommand(InvocationContext ctx, ApplyDeltaCommand command) throws Throwable { entryFactory.wrapEntryForDelta(ctx, command.getDeltaAwareKey(), command.getDelta()); return invokeNextInterceptor(ctx, command); } @Override public Object visitRemoveCommand(InvocationContext ctx, RemoveCommand command) throws Throwable { entryFactory.wrapEntryForRemove(ctx, command.getKey()); return invokeNextAndApplyChanges(ctx, command); } @Override public Object visitReplaceCommand(InvocationContext ctx, ReplaceCommand command) throws Throwable { entryFactory.wrapEntryForReplace(ctx, command.getKey()); return invokeNextAndApplyChanges(ctx, command); } @Override public Object visitPutMapCommand(InvocationContext ctx, PutMapCommand command) throws Throwable { for (Object key : command.getMap().keySet()) { entryFactory.wrapEntryForPut(ctx, key, null, true); } return invokeNextAndApplyChanges(ctx, command); } @Override public Object visitEvictCommand(InvocationContext ctx, EvictCommand command) throws Throwable { return visitRemoveCommand(ctx, command); } protected final void wrapEntriesForPrepare(TxInvocationContext ctx, PrepareCommand command) throws Throwable { if (!ctx.isOriginLocal() || command.isReplayEntryWrapping()) { for (WriteCommand c : command.getModifications()) c.acceptVisitor(ctx, entryWrappingVisitor); } } private Object invokeNextAndApplyChanges(InvocationContext ctx, VisitableCommand command) throws Throwable { final Object result = invokeNextInterceptor(ctx, command); if (!ctx.isInTxScope()) commitContextEntries.commitContextEntries(ctx); return result; } protected class EntryWrappingVisitor extends AbstractVisitor { @Override public final Object visitClearCommand(InvocationContext ctx, ClearCommand command) throws Throwable { boolean notWrapped = false; for (Object key : dataContainer.keySet(null)) { wrapEntryForClear(ctx, key); notWrapped = true; } if (notWrapped) invokeNextInterceptor(ctx, command); return null; } @Override public final Object visitPutMapCommand(InvocationContext ctx, PutMapCommand command) throws Throwable { boolean notWrapped = false; for (Object key : command.getMap().keySet()) { if (cll.localNodeIsOwner(key)) { wrapEntryForPut(ctx, key, false); notWrapped = true; } } if (notWrapped) invokeNextInterceptor(ctx, command); return null; } @Override public final Object visitRemoveCommand(InvocationContext ctx, RemoveCommand command) throws Throwable { if (cll.localNodeIsOwner(command.getKey())) { wrapEntryForRemove(ctx, command.getKey()); invokeNextInterceptor(ctx, command); } return null; } @Override public final Object visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command) throws Throwable { if (cll.localNodeIsOwner(command.getKey())) { wrapEntryForPut(ctx, command.getKey(), command.isPutIfAbsent()); invokeNextInterceptor(ctx, command); } return null; } @Override public final Object visitApplyDeltaCommand(InvocationContext ctx, ApplyDeltaCommand command) throws Throwable { if (cll.localNodeIsOwner(command.getKey())) { entryFactory.wrapEntryForDelta(ctx, command.getDeltaAwareKey(), command.getDelta()); invokeNextInterceptor(ctx, command); } return null; } @Override public final Object visitReplaceCommand(InvocationContext ctx, ReplaceCommand command) throws Throwable { if (cll.localNodeIsOwner(command.getKey())) { wrapEntryForReplace(ctx, command.getKey()); invokeNextInterceptor(ctx, command); } return null; } protected MVCCEntry wrapEntryForReplace(InvocationContext ctx, Object key) throws InterruptedException { return entryFactory.wrapEntryForReplace(ctx, key); } protected MVCCEntry wrapEntryForPut(InvocationContext ctx, Object key, boolean putIfAbsent) throws InterruptedException { return entryFactory.wrapEntryForPut(ctx, key, null, !putIfAbsent); } protected MVCCEntry wrapEntryForRemove(InvocationContext ctx, Object key) throws InterruptedException { return entryFactory.wrapEntryForRemove(ctx, key); } protected MVCCEntry wrapEntryForClear(InvocationContext ctx, Object key) throws InterruptedException { return entryFactory.wrapEntryForClear(ctx, key); } } /** * total order condition: only commits when it is remote context and the prepare has the flag 1PC set * 2PC condition: only commits if the prepare has the flag 1PC set * * @param command the prepare command * @param ctx the invocation context * @return true if the modification should be committed, false otherwise */ protected boolean shouldCommitEntries(PrepareCommand command, TxInvocationContext ctx) { //one phase commit in remote context in total order or it has no modifications (local commands) boolean totalOrder = command.getGlobalTransaction().getReconfigurableProtocol().useTotalOrder(); return (totalOrder && command.isOnePhaseCommit() && (!ctx.isOriginLocal() || !ctx.hasModifications())) || //original condition: one phase commit (!totalOrder && command.isOnePhaseCommit()); } }