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));
}
}
}
}