/*
* ============================================================================
* 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.transaction.impl;
import static org.beanlet.transaction.impl.TransactionHelper.*;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Logger;
import org.beanlet.BeanletValidationException;
import org.jargo.InvocationInterceptor;
import org.jargo.ComponentConfiguration;
import org.jargo.ProxyController;
import org.jargo.deploy.SequentialDeployable;
import org.beanlet.Stateless;
import org.beanlet.annotation.AnnotationDeclaration;
import org.beanlet.annotation.AnnotationDomain;
import org.beanlet.annotation.ElementAnnotation;
import org.beanlet.annotation.MethodElement;
import org.beanlet.annotation.TypeElement;
import org.beanlet.plugin.BeanletConfiguration;
import org.beanlet.transaction.AfterBegin;
import org.beanlet.transaction.AfterCompletion;
import org.beanlet.transaction.BeforeCompletion;
import org.beanlet.transaction.TransactionAttribute;
import org.beanlet.transaction.TransactionSynchronization;
import org.jargo.ConstructorInjection;
import org.jargo.InvocationInterceptorFactory;
import org.jargo.spi.InvocationInterceptorFactoryProvider;
/**
*
* @author Leon van Zantvoort
*/
public final class TransactionInvocationInterceptorFactoryProviderImpl implements
InvocationInterceptorFactoryProvider {
private final Map<Object, Map<TransactionAttribute, TransactionInvocationInterceptor>> interceptorMap;
private static final Logger logger = Logger.getLogger(TransactionInvocationInterceptorFactoryProviderImpl.class.getName());
public TransactionInvocationInterceptorFactoryProviderImpl() {
// PENDING: replace WeakHashMap by a WeakIdentityHashMap.
interceptorMap = Collections.synchronizedMap(
new WeakHashMap<Object, Map<TransactionAttribute, TransactionInvocationInterceptor>>());
}
public Sequence sequence(SequentialDeployable deployable) {
return Sequence.BEFORE;
}
public List<InvocationInterceptorFactory> getInvocationInterceptorFactories(
final ComponentConfiguration<?> configuration, final Method method) {
final List<InvocationInterceptorFactory> factories;
final TransactionAttribute attribute;
if (configuration instanceof BeanletConfiguration) {
AnnotationDeclaration<TransactionAttribute> declaration =
((BeanletConfiguration<?>) configuration).getAnnotationDomain().
getDeclaration(TransactionAttribute.class);
if (declaration.isAnnotationPresent(MethodElement.instance(method))) {
attribute = declaration.getAnnotation(MethodElement.instance(method));
} else {
attribute = declaration.getAnnotation(TypeElement.instance(
configuration.getType()));
}
if (attribute == null) {
factories = Collections.emptyList();
} else {
factories = Collections.singletonList((InvocationInterceptorFactory)
new InvocationInterceptorFactory() {
public Class<?> getType() {
return TransactionInvocationInterceptor.class;
}
public List<InvocationInterceptor> getInvocationInterceptors(
Object instance, ConstructorInjection<?> injection,
ProxyController controller) {
Map<TransactionAttribute, TransactionInvocationInterceptor> map =
interceptorMap.get(instance);
if (map == null) {
map = Collections.synchronizedMap(
new HashMap<TransactionAttribute, TransactionInvocationInterceptor>());
interceptorMap.put(instance, map);
}
TransactionInvocationInterceptor interceptor =
map.get(attribute);
if (interceptor == null) {
interceptor =
new TransactionInvocationInterceptor(
configuration,
getTransactionManager(configuration.getComponentName(), method),
attribute.value(),
attribute.timeout(),
getTransactionSynchronization((BeanletConfiguration<?>) configuration,
new WeakReference<Object>(instance), controller));
map.put(attribute, interceptor);
}
return Collections.singletonList(
(InvocationInterceptor) interceptor);
}
});
}
} else {
factories = Collections.emptyList();
}
return factories;
}
private static TransactionSynchronization getTransactionSynchronization(
BeanletConfiguration<?> configuration,
final WeakReference<Object> ref, final ProxyController controller) {
Class<?> type = configuration.getType();
Method tmpAfterBegin = null;
Method tmpBeforeCompletion = null;
Method tmpAfterCompletion = null;
if (TransactionSynchronization.class.isAssignableFrom(type)) {
try {
tmpAfterBegin = type.getMethod("afterBegin");
tmpBeforeCompletion = type.getMethod("beforeCompletion");
tmpAfterCompletion = type.getMethod("afterCompletion", Boolean.TYPE);
} catch (NoSuchMethodException e) {
assert false : e;
}
}
AnnotationDomain domain = configuration.getAnnotationDomain();
AnnotationDeclaration<AfterBegin> afterBegin = domain.
getDeclaration(AfterBegin.class);
for (ElementAnnotation<MethodElement, AfterBegin> ea :
afterBegin.getTypedElements(MethodElement.class)) {
Method method = ea.getElement().getMethod();
if (method.getParameterTypes().length == 0 &&
method.getReturnType().equals(Void.TYPE)) {
if (!ea.getElement().isOverridden(configuration.getType())) {
if (tmpAfterBegin != null) {
if (!method.getName().equals("afterBegin")) {
throw new BeanletValidationException(configuration.getComponentName(),
"Multiple AfterBegin methods found.");
}
}
tmpAfterBegin = method;
}
} else {
throw new BeanletValidationException(configuration.getComponentName(),
"Invalid signature for AfterBegin method: '" + method + "'.");
}
}
AnnotationDeclaration<BeforeCompletion> beforeCompletion = domain.
getDeclaration(BeforeCompletion.class);
for (ElementAnnotation<MethodElement, BeforeCompletion> ea :
beforeCompletion.getTypedElements(MethodElement.class)) {
Method method = ea.getElement().getMethod();
if (method.getParameterTypes().length == 0 &&
method.getReturnType().equals(Void.TYPE)) {
if (!ea.getElement().isOverridden(configuration.getType())) {
if (tmpBeforeCompletion != null) {
if (!method.getName().equals("beforeCompletion")) {
throw new BeanletValidationException(configuration.getComponentName(),
"Multiple BeforeCompletion methods found.");
}
}
tmpBeforeCompletion = method;
}
} else {
throw new BeanletValidationException(configuration.getComponentName(),
"Invalid signature for BeforeCompletion method: '" + method + "'.");
}
}
AnnotationDeclaration<AfterCompletion> afterCompletion = domain.
getDeclaration(AfterCompletion.class);
for (ElementAnnotation<MethodElement, AfterCompletion> ea :
afterCompletion.getTypedElements(MethodElement.class)) {
Method method = ea.getElement().getMethod();
if (method.getParameterTypes().length == 1 &&
method.getParameterTypes()[0].equals(Boolean.TYPE) &&
method.getReturnType().equals(Void.TYPE)) {
if (!ea.getElement().isOverridden(configuration.getType())) {
if (tmpAfterCompletion != null) {
if (!method.getName().equals("afterCompletion")) {
throw new BeanletValidationException(configuration.getComponentName(),
"Multiple AfterCompletion methods found.");
}
}
tmpAfterCompletion = method;
}
} else {
throw new BeanletValidationException(configuration.getComponentName(),
"Invalid signature for AfterCompletion method: '" + method + "'.");
}
}
final Method afterBeginMethod = tmpAfterBegin;
final Method beforeCompletionMethod = tmpBeforeCompletion;
final Method afterCompletionMethod = tmpAfterCompletion;
final TransactionSynchronization synchronization;
if (afterBeginMethod != null || tmpBeforeCompletion != null ||
tmpAfterCompletion != null) {
if (domain.getDeclaration(Stateless.class).isAnnotationPresent(
TypeElement.instance(configuration.getType()))) {
throw new BeanletValidationException(configuration.getComponentName(),
"TransactionSynchronization callback prohibited for Stateless beanlets.");
}
synchronization = new TransactionSynchronization() {
public void afterBegin() {
if (afterBeginMethod != null) {
if (!afterBeginMethod.isAccessible()) {
afterBeginMethod.setAccessible(true);
}
try {
try {
Object instance = ref.get();
if (instance == null) {
logger.severe("Assertion failed: referent is null on afterBegin.");
} else {
try {
if (controller != null) {
controller.attach(true);
}
afterBeginMethod.invoke(instance);
} finally {
if (controller != null) {
controller.detach();
}
}
}
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
} catch (RuntimeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
}
public void beforeCompletion() {
if (beforeCompletionMethod != null) {
if (!beforeCompletionMethod.isAccessible()) {
beforeCompletionMethod.setAccessible(true);
}
try {
try {
Object instance = ref.get();
if (instance == null) {
logger.severe("Assertion failed: referent is null on beforeCompletion.");
} else {
try {
if (controller != null) {
controller.attach(true);
}
beforeCompletionMethod.invoke(instance);
} finally {
if (controller != null) {
controller.detach();
}
}
}
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
} catch (RuntimeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
}
public void afterCompletion(boolean committed) {
if (afterCompletionMethod != null) {
if (!afterCompletionMethod.isAccessible()) {
afterCompletionMethod.setAccessible(true);
}
try {
try {
Object instance = ref.get();
if (instance == null) {
logger.severe("Assertion failed: referent is null on afterCompletion.");
} else {
try {
if (controller != null) {
controller.attach(true);
}
afterCompletionMethod.invoke(instance, committed);
} finally {
if (controller != null) {
controller.detach();
}
}
}
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
} catch (RuntimeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
}
};
} else {
synchronization = null;
}
return synchronization;
}
}