/*
* JBoss, Home of Professional Open Source
* Copyright 2009 Red Hat Inc. and/or its affiliates and other
* contributors as indicated by the @author tags. All rights reserved.
* See the copyright.txt in the distribution for a full listing of
* individual contributors.
*
* 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 software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.infinispan.interceptors;
import org.infinispan.CacheException;
import org.infinispan.commands.VisitableCommand;
import org.infinispan.config.ConfigurationException;
import org.infinispan.context.InvocationContext;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.factories.annotations.Start;
import org.infinispan.factories.annotations.Stop;
import org.infinispan.factories.components.ComponentMetadataRepo;
import org.infinispan.factories.scopes.Scope;
import org.infinispan.factories.scopes.Scopes;
import org.infinispan.interceptors.base.CommandInterceptor;
import org.infinispan.interceptors.base.ReconfigurableProtocolAwareWrapperInterceptor;
import org.infinispan.reconfigurableprotocol.ReconfigurableProtocol;
import org.infinispan.reconfigurableprotocol.manager.ReconfigurableReplicationManager;
import org.infinispan.util.ReflectionUtil;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
/**
* Knows how to build and manage an chain of interceptors. Also in charge with invoking methods on the chain.
*
* @author Mircea.Markus@jboss.com
* @author Galder ZamarreƱo
* @since 4.0
*/
@Scope(Scopes.NAMED_CACHE)
public class InterceptorChain {
private static final Log log = LogFactory.getLog(InterceptorChain.class);
public static enum InterceptorType {
STATE_TRANSFER,
CUSTOM_INTERCEPTOR_BEFORE_TX_INTERCEPTOR,
CUSTOM_INTERCEPTOR_AFTER_TX_INTERCEPTOR,
LOCKING,
WRAPPER,
DEADLOCK,
CLUSTER
}
private final Map<InterceptorType, ReconfigurableProtocolAwareWrapperInterceptor> wrappers =
new EnumMap<InterceptorType, ReconfigurableProtocolAwareWrapperInterceptor>(InterceptorType.class);
private ReconfigurableReplicationManager manager;
/**
* reference to the first interceptor in the chain
*/
private volatile CommandInterceptor firstInChain;
final ReentrantLock lock = new ReentrantLock();
/**
* Constructs an interceptor chain having the supplied interceptor as first.
*/
public InterceptorChain(CommandInterceptor first) {
this.firstInChain = first;
for (InterceptorType type : InterceptorType.values()) {
wrappers.put(type, new ReconfigurableProtocolAwareWrapperInterceptor());
}
}
/**
* Constructs an interceptor chain having the supplied interceptor as first.
*/
public InterceptorChain() {
for (InterceptorType type : InterceptorType.values()) {
wrappers.put(type, new ReconfigurableProtocolAwareWrapperInterceptor());
}
}
@Inject
public void inject(ReconfigurableReplicationManager manager) {
this.manager = manager;
}
@Start
private void printChainInfo() {
if (log.isDebugEnabled()) {
log.debugf("Interceptor chain size: %d", size());
log.debugf("Interceptor chain is: %s", toString());
}
}
public void setFirst(CommandInterceptor commandInterceptor) {
this.firstInChain = commandInterceptor;
}
private void validateCustomInterceptor(Class<? extends CommandInterceptor> i) {
if ((!ReflectionUtil.getAllMethodsShallow(i, Inject.class).isEmpty() ||
!ReflectionUtil.getAllMethodsShallow(i, Start.class).isEmpty() ||
!ReflectionUtil.getAllMethodsShallow(i, Stop.class).isEmpty()) &&
ComponentMetadataRepo.findComponentMetadata(i.getName()) == null) {
log.customInterceptorExpectsInjection(i.getName());
}
}
/**
* Ensures that the interceptor of type passed in isn't already added
*
* @param clazz type of interceptor to check for
*/
private void assertNotAdded(Class<? extends CommandInterceptor> clazz) {
CommandInterceptor next = firstInChain;
while (next != null) {
if (next.getClass().equals(clazz))
throw new ConfigurationException("Detected interceptor of type [" + clazz.getName() + "] being added to the interceptor chain more than once!");
next = next.getNext();
}
}
/**
* Inserts the given interceptor at the specified position in the chain (o based indexing).
*
* @throws IllegalArgumentException if the position is invalid (e.g. 5 and there are only 2 interceptors in the
* chain)
*/
public void addInterceptor(CommandInterceptor interceptor, int position) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Class<? extends CommandInterceptor> interceptorClass = interceptor.getClass();
assertNotAdded(interceptorClass);
validateCustomInterceptor(interceptorClass);
if (position == 0) {
interceptor.setNext(firstInChain);
firstInChain = interceptor;
return;
}
if (firstInChain == null) return;
CommandInterceptor it = firstInChain;
int index = 0;
while (it != null) {
if (++index == position) {
interceptor.setNext(it.getNext());
it.setNext(interceptor);
return;
}
it = it.getNext();
}
throw new IllegalArgumentException("Invalid index: " + index + " !");
} finally {
lock.unlock();
}
}
/**
* Removes the interceptor at the given postion.
*
* @throws IllegalArgumentException if the position is invalid (e.g. 5 and there are only 2 interceptors in the
* chain)
*/
public void removeInterceptor(int position) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (firstInChain == null) return;
if (position == 0) {
firstInChain = firstInChain.getNext();
return;
}
CommandInterceptor it = firstInChain;
int index = 0;
while (it != null) {
if (++index == position) {
if (it.getNext() == null) return; //nothing to remove
it.setNext(it.getNext().getNext());
return;
}
it = it.getNext();
}
throw new IllegalArgumentException("Invalid position: " + position + " !");
} finally {
lock.unlock();
}
}
/**
* Returns the number of interceptors in the chain.
*/
public int size() {
int size = 0;
CommandInterceptor it = firstInChain;
while (it != null) {
size++;
it = it.getNext();
}
return size;
}
/**
* Returns an unmofiable list with all the interceptors in sequence. If first in chain is null an empty list is
* returned.
*/
public List<CommandInterceptor> asList() {
if (firstInChain == null) return Collections.emptyList();
List<CommandInterceptor> retval = new LinkedList<CommandInterceptor>();
CommandInterceptor tmp = firstInChain;
do {
retval.add(tmp);
tmp = tmp.getNext();
}
while (tmp != null);
return Collections.unmodifiableList(retval);
}
/**
* Removes all the occurences of supplied interceptor type from the chain.
*/
public void removeInterceptor(Class<? extends CommandInterceptor> clazz) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (isFirstInChain(clazz)) {
firstInChain = firstInChain.getNext();
}
CommandInterceptor it = firstInChain.getNext();
CommandInterceptor prevIt = firstInChain;
while (it != null) {
if (it.getClass() == clazz) {
prevIt.setNext(it.getNext());
}
prevIt = it;
it = it.getNext();
}
} finally {
lock.unlock();
}
}
protected boolean isFirstInChain(Class<? extends CommandInterceptor> clazz) {
return firstInChain.getClass() == clazz;
}
/**
* Adds a new interceptor in list after an interceptor of a given type.
*
* @return true if the interceptor was added; i.e. the afterInterceptor exists
*/
public boolean addInterceptorAfter(CommandInterceptor toAdd, Class<? extends CommandInterceptor> afterInterceptor) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Class<? extends CommandInterceptor> interceptorClass = toAdd.getClass();
assertNotAdded(interceptorClass);
validateCustomInterceptor(interceptorClass);
CommandInterceptor it = firstInChain;
while (it != null) {
if (it.getClass().equals(afterInterceptor)) {
toAdd.setNext(it.getNext());
it.setNext(toAdd);
return true;
}
it = it.getNext();
}
return false;
} finally {
lock.unlock();
}
}
/**
* Adds a new interceptor in list after an interceptor of a given type.
*
* @return true if the interceptor was added; i.e. the afterInterceptor exists
*/
public boolean addInterceptorBefore(CommandInterceptor toAdd, Class<? extends CommandInterceptor> beforeInterceptor) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Class<? extends CommandInterceptor> interceptorClass = toAdd.getClass();
assertNotAdded(interceptorClass);
validateCustomInterceptor(interceptorClass);
if (firstInChain.getClass().equals(beforeInterceptor)) {
toAdd.setNext(firstInChain);
firstInChain = toAdd;
return true;
}
CommandInterceptor it = firstInChain;
while (it.getNext() != null) {
if (it.getNext().getClass().equals(beforeInterceptor)) {
toAdd.setNext(it.getNext());
it.setNext(toAdd);
return true;
}
it = it.getNext();
}
return false;
} finally {
lock.unlock();
}
}
/**
* Replaces an existing interceptor of the given type in the interceptor chain with a new interceptor instance passed as parameter.
*
* @param replacingInterceptor the interceptor to add to the interceptor chain
* @param toBeReplacedInterceptorType the type of interceptor that should be swapped with the new one
* @return true if the interceptor was replaced
*/
public boolean replaceInterceptor(CommandInterceptor replacingInterceptor, Class<? extends CommandInterceptor> toBeReplacedInterceptorType) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Class<? extends CommandInterceptor> interceptorClass = replacingInterceptor.getClass();
assertNotAdded(interceptorClass);
validateCustomInterceptor(interceptorClass);
if (firstInChain.getClass().equals(toBeReplacedInterceptorType)) {
replacingInterceptor.setNext(firstInChain.getNext());
firstInChain = replacingInterceptor;
return true;
}
CommandInterceptor it = firstInChain;
CommandInterceptor previous = firstInChain;
while (it.getNext() != null) {
CommandInterceptor current = it.getNext();
if (current.getClass().equals(toBeReplacedInterceptorType)) {
replacingInterceptor.setNext(current.getNext());
previous.setNext(replacingInterceptor);
return true;
}
previous = current;
it = current;
}
return false;
} finally {
lock.unlock();
}
}
/**
* Appends at the end.
*/
public void appendInterceptor(CommandInterceptor ci, boolean isCustom) {
if (isCustom) validateCustomInterceptor(ci.getClass());
// Called when building interceptor chain and so concurrent start calls are protected already
CommandInterceptor it = firstInChain;
while (it.hasNext()) it = it.getNext();
it.setNext(ci);
// make sure we nullify the "next" pointer in the last interceptors.
ci.setNext(null);
}
/**
* Walks the command through the interceptor chain. The received ctx is being passed in.
*/
public Object invoke(InvocationContext ctx, VisitableCommand command) {
try {
ctx.setProtocolId(manager.getCurrentProtocolId());
return command.acceptVisitor(ctx, firstInChain);
} catch (CacheException e) {
if (e.getCause() instanceof InterruptedException)
Thread.currentThread().interrupt();
throw e;
} catch (RuntimeException e) {
throw e;
} catch (Throwable t) {
throw new CacheException(t);
}
}
/**
* @return the first interceptor in the chain.
*/
public CommandInterceptor getFirstInChain() {
return firstInChain;
}
/**
* Mainly used by unit tests to replace the interceptor chain with the starting point passed in.
*
* @param interceptor interceptor to be used as the first interceptor in the chain.
*/
public void setFirstInChain(CommandInterceptor interceptor) {
this.firstInChain = interceptor;
}
/**
* Returns all interceptors which extend the given command interceptor.
*/
public List<CommandInterceptor> getInterceptorsWhichExtend(Class<? extends CommandInterceptor> interceptorClass) {
List<CommandInterceptor> result = new LinkedList<CommandInterceptor>();
for (CommandInterceptor interceptor : asList()) {
boolean isSubclass = interceptorClass.isAssignableFrom(interceptor.getClass());
if (isSubclass) {
result.add(interceptor);
}
}
return result;
}
/**
* Returns all the interceptors that have the fully qualified name of their class equal with the supplied class
* name.
*/
public List<CommandInterceptor> getInterceptorsWithClassName(String name) {
// Called when building interceptor chain and so concurrent start calls are protected already
CommandInterceptor iterator = firstInChain;
List<CommandInterceptor> result = new ArrayList<CommandInterceptor>(2);
while (iterator != null) {
if (iterator.getClass().getName().equals(name)) result.add(iterator);
iterator = iterator.getNext();
}
return result;
}
public String toString() {
StringBuilder sb = new StringBuilder();
CommandInterceptor i = firstInChain;
while (i != null) {
sb.append("\n\t>> ");
sb.append(i.getClass().getName());
if (i instanceof ReconfigurableProtocolAwareWrapperInterceptor) {
sb.append(((ReconfigurableProtocolAwareWrapperInterceptor) i).routeTableToString());
}
i = i.getNext();
}
return sb.toString();
}
/**
* Checks whether the chain contains the supplied interceptor instance.
*/
public boolean containsInstance(CommandInterceptor interceptor) {
CommandInterceptor it = firstInChain;
while (it != null) {
if (it == interceptor) return true;
it = it.getNext();
}
return false;
}
public boolean containsInterceptorType(Class<? extends CommandInterceptor> interceptorType) {
// Called when building interceptor chain and so concurrent start calls are protected already
CommandInterceptor it = firstInChain;
while (it != null) {
if (it.getClass().equals(interceptorType)) return true;
it = it.getNext();
}
return false;
}
public void appendWrapper(InterceptorType type) {
appendInterceptor(wrappers.get(type), false);
}
public void registerNewProtocol(ReconfigurableProtocol protocol) {
EnumMap<InterceptorType, CommandInterceptor> newInterceptors = protocol.buildInterceptorChain();
for (Map.Entry<InterceptorType, CommandInterceptor> entry : newInterceptors.entrySet()) {
wrappers.get(entry.getKey()).setProtocolDependentInterceptor(protocol.getUniqueProtocolName(), entry.getValue());
}
}
}