package org.infinispan.test.concurrent; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.List; import org.infinispan.manager.EmbeddedCacheManager; import org.infinispan.test.TestingUtil; /** * Replaces a global component with a dynamic proxy that can interact with a {@link StateSequencer} when a method that * matches a {@link InvocationMatcher} is called. * * @author Dan Berindei * @since 7.0 */ public class GlobalComponentSequencerAction<T> { protected final StateSequencer stateSequencer; protected final EmbeddedCacheManager cacheManager; protected final Class<T> componentClass; protected final InvocationMatcher matcher; protected ProxyInvocationHandler ourHandler; GlobalComponentSequencerAction(StateSequencer stateSequencer, EmbeddedCacheManager cacheManager, Class<T> componentClass, InvocationMatcher matcher) { this.matcher = matcher; this.componentClass = componentClass; this.stateSequencer = stateSequencer; this.cacheManager = cacheManager; } /** * 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 GlobalComponentSequencerAction before(String state1, String... additionalStates) { replaceComponent(); ourHandler.beforeStates(StateSequencerUtil.concat(state1, additionalStates)); return this; } protected void replaceComponent() { if (ourHandler == null) { T component = cacheManager.getGlobalComponentRegistry().getComponent(componentClass); if (component == null) { throw new IllegalStateException("Attempting to wrap a non-existing global component: " + componentClass); } ourHandler = new ProxyInvocationHandler(component, stateSequencer, matcher); T componentProxy = createComponentProxy(componentClass, ourHandler); TestingUtil.replaceComponent(cacheManager, componentClass, componentProxy, true); } } protected <T> T createComponentProxy(Class<T> componentClass, InvocationHandler handler) { return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{componentClass}, handler); } /** * 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 GlobalComponentSequencerAction<T> after(String state1, String... additionalStates) { replaceComponent(); ourHandler.afterStates(StateSequencerUtil.concat(state1, additionalStates)); return this; } public static class ProxyInvocationHandler implements InvocationHandler { private final Object wrappedInstance; private final StateSequencer stateSequencer; private final InvocationMatcher matcher; private volatile List<String> statesBefore; private volatile List<String> statesAfter; public ProxyInvocationHandler(Object wrappedInstance, StateSequencer stateSequencer, InvocationMatcher matcher) { this.wrappedInstance = wrappedInstance; this.stateSequencer = stateSequencer; this.matcher = matcher; } public void beforeStates(List<String> states) { this.statesBefore = StateSequencerUtil.listCopy(states); } public void afterStates(List<String> states) { this.statesAfter = StateSequencerUtil.listCopy(states); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { boolean matches = matcher.accept(wrappedInstance, method.getName(), args); StateSequencerUtil.advanceMultiple(stateSequencer, matches, statesBefore); try { return method.invoke(wrappedInstance, args); } finally { StateSequencerUtil.advanceMultiple(stateSequencer, matches, statesAfter); } } } }