package org.infinispan.test.concurrent; import static org.infinispan.test.TestingUtil.wrapInboundInvocationHandler; import java.util.List; import java.util.concurrent.TimeoutException; import org.infinispan.Cache; import org.infinispan.commands.remote.CacheRpcCommand; import org.infinispan.remoting.inboundhandler.DeliverOrder; import org.infinispan.remoting.inboundhandler.PerCacheInboundInvocationHandler; import org.infinispan.remoting.inboundhandler.Reply; import org.infinispan.remoting.responses.ExceptionResponse; /** * Replaces the {@link org.infinispan.remoting.inboundhandler.PerCacheInboundInvocationHandler} with a wrapper that can interact with a {@link StateSequencer} when a * command that matches a {@link CommandMatcher} is invoked. * * @author Dan Berindei * @since 7.0 */ public class InboundRpcSequencerAction { private final StateSequencer stateSequencer; private final Cache<?,?> cache; private final CommandMatcher matcher; private SequencerPerCacheInboundInvocationHandler ourHandler; public InboundRpcSequencerAction(StateSequencer stateSequencer, Cache cache, CommandMatcher matcher) { this.stateSequencer = stateSequencer; this.cache = cache; this.matcher = matcher; } /** * Set up a list of sequencer states before interceptor {@code interceptorClass} is called. * <p/> * Each invocation accepted by {@code matcher} will enter/exit the next state from the list, and does nothing after the list is exhausted. */ public InboundRpcSequencerAction before(String state1, String... additionalStates) { replaceInboundInvocationHandler(); ourHandler.beforeStates(StateSequencerUtil.concat(state1, additionalStates)); return this; } private void replaceInboundInvocationHandler() { if (ourHandler == null) { ourHandler = wrapInboundInvocationHandler(cache, handler -> new SequencerPerCacheInboundInvocationHandler(handler, stateSequencer, matcher)); } } /** * Set up a list of sequencer states after interceptor {@code interceptorClass} has returned. * <p/> * Each invocation accepted by {@code matcher} will enter/exit the next state from the list, and does nothing after the list is exhausted. */ public InboundRpcSequencerAction after(String state1, String... additionalStates) { replaceInboundInvocationHandler(); ourHandler.afterStates(StateSequencerUtil.concat(state1, additionalStates)); return this; } public static class SequencerPerCacheInboundInvocationHandler implements PerCacheInboundInvocationHandler { private final StateSequencer stateSequencer; private final CommandMatcher matcher; private final PerCacheInboundInvocationHandler handler; private volatile List<String> statesBefore; private volatile List<String> statesAfter; public SequencerPerCacheInboundInvocationHandler(PerCacheInboundInvocationHandler handler, StateSequencer stateSequencer, CommandMatcher matcher) { this.handler = handler; this.stateSequencer = stateSequencer; this.matcher = matcher; } @Override public void handle(CacheRpcCommand command, Reply reply, DeliverOrder order) { boolean accepted = matcher.accept(command); advance(accepted, statesBefore, reply); try { handler.handle(command, reply, order); } finally { advance(accepted, statesAfter, new Reply() { @Override public void reply(Object returnValue) { //no-op } }); } } public void beforeStates(List<String> states) { this.statesBefore = StateSequencerUtil.listCopy(states); } public void afterStates(List<String> states) { this.statesAfter = StateSequencerUtil.listCopy(states); } private void advance(boolean accepted, List<String> states, Reply reply) { try { StateSequencerUtil.advanceMultiple(stateSequencer, accepted, states); } catch (TimeoutException e) { reply.reply(new ExceptionResponse(e)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); reply.reply(new ExceptionResponse(e)); } } } }