/*
* ============================================================================
* GNU Lesser General Public License
* ============================================================================
*
* Beanlet - JSE Application Container.
* Copyright (C) 2006 Leon van Zantvoort
*
* This library 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 library 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Leon van Zantvoort
* 243 Acalanes Drive #11
* Sunnyvale, CA 94086
* USA
*
* zantvoort@users.sourceforge.net
* http://beanlet.org
*/
package org.beanlet.impl;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import org.beanlet.common.AbstractProvider;
import org.beanlet.common.InvocationImpl;
import org.jargo.ComponentConfiguration;
import org.beanlet.AroundInvoke;
import org.beanlet.BeanletException;
import org.beanlet.BeanletValidationException;
import org.beanlet.ExcludeClassInterceptors;
import org.beanlet.ExcludeDefaultInterceptors;
import org.beanlet.Interceptor;
import org.beanlet.Interceptors;
import org.beanlet.InvocationContext;
import org.beanlet.annotation.AnnotationDeclaration;
import org.beanlet.annotation.AnnotationDomain;
import org.beanlet.annotation.ElementAnnotation;
import org.beanlet.annotation.MethodElement;
import org.beanlet.annotation.PackageElement;
import org.beanlet.annotation.TypeElement;
import org.beanlet.plugin.BeanletConfiguration;
import org.jargo.ConstructorInjection;
import org.jargo.spi.InvocationInterceptorFactoryProvider;
import org.jargo.InvocationInterceptor;
import org.jargo.InvocationInterceptorAdapter;
import org.jargo.InvocationInterceptorFactory;
import org.jargo.ProxyController;
/**
* @author Leon van Zantvoort
*/
public final class InvocationInterceptorFactoryProviderImpl extends AbstractProvider
implements InvocationInterceptorFactoryProvider {
private static final Method INTERCEPT_METHOD;
static {
try {
INTERCEPT_METHOD = Interceptor.class.getMethod(
"intercept", InvocationContext.class);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
// PENDING: apply equality check based on identity on this map?
private final Map<Object, Map<Class, List<Interceptor>>> interceptorMap;
public InvocationInterceptorFactoryProviderImpl() {
// PENDING: replace WeakHashMap by a WeakIdentityHashMap.
interceptorMap = Collections.synchronizedMap(
new WeakHashMap<Object, Map<Class, List<Interceptor>>>());
}
public List<InvocationInterceptorFactory> getInvocationInterceptorFactories(
ComponentConfiguration configuration, Method method) {
List<InvocationInterceptorFactory> list =
new ArrayList<InvocationInterceptorFactory>();
List<InvocationInterceptorFactory> factories =
getInterceptorFactories(configuration, method);
list.addAll(factories);
List<InvocationInterceptorFactory> defaultFactories =
getDefaultInterceptorFactories(configuration, method);
for (Iterator<InvocationInterceptorFactory> i =
defaultFactories.iterator(); i.hasNext();) {
InvocationInterceptorFactory factory = i.next();
for (InvocationInterceptorFactory f : list) {
if (f.getType().equals(factory.getType())) {
i.remove();
break;
}
}
}
list.addAll(0, defaultFactories);
InvocationInterceptorFactory localFactory =
getLocalInterceptorFactory(configuration);
if (localFactory != null) {
list.add(localFactory);
}
return Collections.unmodifiableList(list);
}
private List<InvocationInterceptorFactory> getDefaultInterceptorFactories(
final ComponentConfiguration configuration, Method method) {
List<InvocationInterceptorFactory> factories =
new ArrayList<InvocationInterceptorFactory>();
if (configuration instanceof BeanletConfiguration) {
AnnotationDomain domain = ((BeanletConfiguration)
configuration).getAnnotationDomain();
Package pkg = configuration.getType().getPackage();
if (pkg != null && !domain.getDeclaration(
ExcludeDefaultInterceptors.class).isAnnotationPresent(
MethodElement.instance(method))) {
Interceptors annotation = domain.
getDeclaration(Interceptors.class).
getAnnotation(PackageElement.instance(pkg));
if (annotation != null) {
for (final Class<?> cls : annotation.value()) {
factories.add(new InvocationInterceptorFactory() {
public Class<?> getType() {
return cls;
}
public List<InvocationInterceptor> getInvocationInterceptors(
Object instance,
ConstructorInjection injection,
ProxyController controller) {
return Collections.unmodifiableList(transformInterceptors(
createInterceptors(instance, injection, cls,
(BeanletConfiguration) configuration), true));
}
});
}
}
}
}
return factories;
}
private List<InvocationInterceptorFactory> getInterceptorFactories(
final ComponentConfiguration configuration, Method method) {
List<InvocationInterceptorFactory> factories =
new ArrayList<InvocationInterceptorFactory>();
if (configuration instanceof BeanletConfiguration) {
final Set<Class> lifecycleInterceptors = new HashSet<Class>();
LinkedHashSet<Class> interceptors = new LinkedHashSet<Class>();
AnnotationDomain domain = ((BeanletConfiguration)
configuration).getAnnotationDomain();
if (!domain.getDeclaration(ExcludeClassInterceptors.class).
isAnnotationPresent(MethodElement.instance(method))) {
Interceptors annotation = domain.
getDeclaration(Interceptors.class).getAnnotation(
TypeElement.instance(configuration.getType()));
if (annotation != null) {
lifecycleInterceptors.addAll(Arrays.asList(annotation.value()));
interceptors.addAll(Arrays.asList(annotation.value()));
}
}
Interceptors annotation = domain.
getDeclaration(Interceptors.class).getAnnotation(
MethodElement.instance(method));
if (annotation != null) {
for (Class<?> cls : annotation.value()) {
if (interceptors.contains(cls)) {
interceptors.remove(cls);
}
interceptors.add(cls);
}
}
for (final Class<?> cls : interceptors) {
factories.add(new InvocationInterceptorFactory() {
public Class<?> getType() {
return cls;
}
public List<InvocationInterceptor> getInvocationInterceptors(
Object instance,
ConstructorInjection injection,
ProxyController controller) {
return Collections.unmodifiableList(transformInterceptors(
createInterceptors(instance, injection, cls,
(BeanletConfiguration) configuration),
lifecycleInterceptors.contains(cls)));
}
});
}
}
return factories;
}
/**
* Creates local interceptor (list) from the specified
* {@code instance}. An empty list is returned if {@code instance} does not
* implement an internal interceptor.
*/
private InvocationInterceptorFactory getLocalInterceptorFactory(
final ComponentConfiguration configuration) {
InvocationInterceptorFactory factory = null;
if (configuration instanceof BeanletConfiguration) {
if (Interceptor.class.isAssignableFrom(configuration.getType())) {
final ThreadLocal<org.jargo.InvocationContext> local =
new ThreadLocal<org.jargo.InvocationContext>();
final InvocationContext newCtx = new InvocationContextImpl() {
protected org.jargo.InvocationContext
getInvocationContext() {
return local.get();
}
};
factory = new InvocationInterceptorFactory() {
public Class<?> getType() {
return configuration.getType();
}
public List<InvocationInterceptor> getInvocationInterceptors(
final Object instance,
ConstructorInjection injection,
ProxyController controller) {
return Collections.singletonList((InvocationInterceptor) new InvocationInterceptorAdapter() {
public Object getInstance() {
return instance;
}
public boolean isLifecycleInterceptor() {
// Local interceptors are per definition no callback interceptor.
return false;
}
public Object intercept(final org.jargo.InvocationContext ctx)
throws Exception {
local.set(ctx);
return ((Interceptor) instance).intercept(newCtx);
}
public String toString() {
return "InvocationInterceptorAdapter[" + getInstance() + "]@" +
Integer.toHexString(System.identityHashCode(this));
}
});
}
};
}
AnnotationDomain domain = ((BeanletConfiguration) configuration).
getAnnotationDomain();
List<ElementAnnotation<MethodElement, AroundInvoke>> list = domain.
getDeclaration(AroundInvoke.class).getTypedElements(
MethodElement.class, configuration.getType());
if (list.size() > 1) {
throw new BeanletValidationException(configuration.getComponentName(),
"Class MAY declare one AroundInvoke method: '" + configuration.getType() + "'.");
}
if (!list.isEmpty()) {
final Method method = list.get(0).getElement().getMethod();
if (Modifier.isStatic(method.getModifiers())) {
throw new BeanletValidationException(configuration.getComponentName(),
"Method MUST NOT be static: '" + method + "'.");
}
Class<?>[] types = method.getParameterTypes();
if (types.length != 1 || !types[0].equals(
InvocationContext.class)) {
throw new BeanletValidationException(configuration.getComponentName(),
"Method MUST specify InvocationContext as parameter: '" + method + "'.");
}
final ThreadLocal<org.jargo.InvocationContext> local =
new ThreadLocal<org.jargo.InvocationContext>();
final InvocationContext newCtx = new InvocationContextImpl() {
protected org.jargo.InvocationContext
getInvocationContext() {
return local.get();
}
};
factory = new InvocationInterceptorFactory() {
public Class<?> getType() {
return configuration.getType();
}
public List<InvocationInterceptor> getInvocationInterceptors(
final Object instance,
ConstructorInjection injection,
ProxyController controller) {
return Collections.singletonList((InvocationInterceptor) new InvocationInterceptorAdapter() {
public Object getInstance() {
return instance;
}
public boolean isLifecycleInterceptor() {
// Local interceptors are per definition no callback interceptor.
return false;
}
public Object intercept(final org.jargo.InvocationContext ctx)
throws Exception {
local.set(ctx);
return InvocationImpl.invoke(instance, method, newCtx);
}
public String toString() {
return "InvocationInterceptorAdapter[" + getInstance() + "]@" +
Integer.toHexString(System.identityHashCode(this));
}
});
}
};
}
}
return factory;
}
private List<InvocationInterceptor> transformInterceptors(
List<Interceptor> interceptors, final boolean lifecycleInterceptor) {
List<InvocationInterceptor> transformed =
new ArrayList<InvocationInterceptor>();
for (final Interceptor interceptor : interceptors) {
final ThreadLocal<org.jargo.InvocationContext> local =
new ThreadLocal<org.jargo.InvocationContext>();
final InvocationContext newCtx = new InvocationContextImpl() {
protected org.jargo.InvocationContext
getInvocationContext() {
return local.get();
}
};
transformed.add(new InvocationInterceptorAdapter() {
public Object getInstance() {
final Object instance;
if (interceptor instanceof InterceptorAdapter) {
instance = ((InterceptorAdapter) interceptor).getInstance();
} else {
instance = interceptor;
}
return instance;
}
public boolean isLifecycleInterceptor() {
return lifecycleInterceptor;
}
public Object intercept(final org.jargo.InvocationContext ctx)
throws Exception {
local.set(ctx);
return interceptor.intercept(newCtx);
}
public String toString() {
return "InvocationInterceptorAdapter[" + getInstance() + "]@" +
Integer.toHexString(System.identityHashCode(this));
}
});
}
return transformed;
}
private List<Interceptor> createInterceptors(Object instance,
ConstructorInjection injection, Class<?> cls,
BeanletConfiguration configuration) {
final List<Interceptor> interceptors;
Map<Class, List<Interceptor>> map = interceptorMap.get(instance);
if (map != null && map.containsKey(cls)) {
interceptors = map.get(cls);
} else {
interceptors = new ArrayList<Interceptor>();
String beanletName = configuration.getComponentName();
final Object o;
if (injection != null) {
o = injection.inject();
} else {
try {
o = cls.newInstance();
} catch (IllegalAccessException e) {
throw new BeanletException(beanletName, e);
} catch (InstantiationException e) {
throw new BeanletException(beanletName, e);
}
}
LinkedList<Class> classes = new LinkedList<Class>();
Class<?> tmp = cls;
do {
classes.addFirst(tmp);
} while ((tmp = tmp.getSuperclass()) != null);
AnnotationDeclaration<AroundInvoke> declaration = configuration.
getAnnotationDomain().getDeclaration(AroundInvoke.class);
for (Class c : classes) {
AroundInvoke aroundInvoke = null;
for (ElementAnnotation<MethodElement, AroundInvoke> ea :
declaration.getTypedElements(MethodElement.class, c)) {
final Method method = ea.getElement().getMethod();
if (method.getDeclaringClass().equals(c)) {
if (aroundInvoke != null) {
throw new BeanletValidationException(beanletName,
method.getName() +
"Class MAY declare only one AroundInvoke method: '" + c + "'.");
}
aroundInvoke = ea.getAnnotation();
Class<?>[] types = method.getParameterTypes();
if (types.length != 1 || !types[0].equals(
InvocationContext.class)) {
throw new BeanletValidationException(beanletName,
"Method MUST " +
"have InterceptorContext object " +
"as parameter: '" + method + "'.");
}
if (!Object.class.isAssignableFrom(method.getReturnType())) {
throw new BeanletValidationException(beanletName,
"Return type of method " +
"MUST be Object (or any subclass): '" + method + "'.");
}
if (Modifier.isStatic(method.getModifiers())) {
throw new BeanletValidationException(beanletName,
"Method MUST NOT be static: '" + method + "'.");
}
if (!ea.getElement().isOverridden(cls)) {
if (o instanceof Interceptor) {
if (!method.getName().equals(INTERCEPT_METHOD.getName())) {
throw new BeanletValidationException(beanletName,
"ArroundInvoke and " + Interceptor.class.getName() +
" mixture: '" + method + "'.");
} else {
// Interceptor is added after loop.
continue;
}
}
interceptors.add(new InterceptorAdapter() {
public Object intercept(InvocationContext ctx)
throws Exception {
return InvocationImpl.invoke(o, method, ctx);
}
public Object getInstance() {
return o;
}
public String toString() {
return "InvocationInterceptorAdapter[" + getInstance() + "]@" +
Integer.toHexString(System.identityHashCode(this));
}
});
}
}
}
}
if (o instanceof Interceptor) {
assert interceptors.isEmpty();
interceptors.add((Interceptor) o);
}
if (interceptors.isEmpty()) {
interceptors.add(new InterceptorAdapter() {
public Object intercept(InvocationContext ctx)
throws Exception {
return ctx.proceed();
}
public Object getInstance() {
return o;
}
public String toString() {
return "InvocationInterceptorAdapter[" + getInstance() + "]@" +
Integer.toHexString(System.identityHashCode(this));
}
});
}
if (map == null) {
map = new ConcurrentHashMap<Class, List<Interceptor>>();
interceptorMap.put(instance, map);
}
map.put(cls, interceptors);
}
return interceptors;
}
}