/**
* Copyright (c) 2013-2016, The SeedStack authors <http://seedstack.org>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.seedstack.seed.core.internal.transaction;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.name.Names;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.seedstack.seed.SeedException;
import org.seedstack.seed.transaction.Transactional;
import org.seedstack.seed.transaction.spi.ExceptionHandler;
import org.seedstack.seed.transaction.spi.TransactionHandler;
import org.seedstack.seed.transaction.spi.TransactionManager;
import org.seedstack.seed.transaction.spi.TransactionMetadata;
import org.seedstack.seed.transaction.spi.TransactionMetadataResolver;
import javax.inject.Inject;
import java.lang.reflect.Method;
import java.util.Optional;
import java.util.Set;
/**
* Base class for common transaction manager behavior.
*/
public abstract class AbstractTransactionManager implements TransactionManager {
private final MethodInterceptorImplementation methodInterceptorImplementation = new MethodInterceptorImplementation();
@Inject
protected Injector injector;
@Inject
private Set<TransactionMetadataResolver> transactionMetadataResolvers;
private final class MethodInterceptorImplementation implements MethodInterceptor {
@Override
@SuppressWarnings("unchecked")
public Object invoke(MethodInvocation invocation) throws Throwable {
TransactionLogger transactionLogger = new TransactionLogger();
transactionLogger.log("intercepting {}#{}", invocation.getMethod().getDeclaringClass().getCanonicalName(), invocation.getMethod().getName());
TransactionMetadata transactionMetadata = readTransactionMetadata(invocation);
transactionLogger.log("{}", transactionMetadata);
TransactionHandler<Object> transactionHandler;
try {
transactionHandler = getTransactionHandler(transactionMetadata.getHandler(), transactionMetadata.getResource());
} catch (SeedException e) {
throw e.put("method", invocation.getMethod().toString());
}
transactionLogger.log("using {} transaction handler", transactionHandler.getClass().getCanonicalName());
try {
return doMethodInterception(transactionLogger, invocation, transactionMetadata, transactionHandler);
} catch (SeedException e) {
throw e.put("method", invocation.getMethod().toString());
}
}
}
@Override
public MethodInterceptor getMethodInterceptor() {
return methodInterceptorImplementation;
}
/**
* This method provide the technology-specific interception behavior.
*
* @param transactionLogger The object that must be used to log transaction progress.
* @param invocation The method interception object.
* @param transactionMetadata Metadata of the current transaction.
* @param transactionHandler Transaction handler for the current transacted resource.
* @return the value of the method invocation
* @throws Throwable if any problem occurs during interception.
*/
protected abstract Object doMethodInterception(TransactionLogger transactionLogger, MethodInvocation invocation, TransactionMetadata transactionMetadata, TransactionHandler<Object> transactionHandler) throws Throwable;
/**
* This method call the wrapped transactional method.
*
* @param transactionLogger The object that must be used to log transaction progress.
* @param invocation the {@link MethodInvocation} denoting the transactional method.
* @param transactionMetadata the current transaction metadata.
* @param currentTransaction the current transaction object if any.
* @return the return value of the transactional method.
* @throws Throwable if an exception occurs during the method invocation.
*/
protected Object doInvocation(TransactionLogger transactionLogger, MethodInvocation invocation, TransactionMetadata transactionMetadata, Object currentTransaction) throws Throwable {
Object result = null;
try {
transactionLogger.log("invocation started", transactionLogger);
result = invocation.proceed();
transactionLogger.log("invocation ended", transactionLogger);
} catch (Exception exception) {
doHandleException(transactionLogger, exception, transactionMetadata, currentTransaction);
}
return result;
}
@SuppressWarnings("unchecked")
private void doHandleException(TransactionLogger transactionLogger, Exception exception, TransactionMetadata transactionMetadata, Object currentTransaction) throws Exception {
boolean matchForRollback = false, matchForNoRollback = false;
// Check for the need to rollback
for (Class<? extends Exception> rollbackExceptionClass : transactionMetadata.getRollbackOn()) {
if (rollbackExceptionClass.isAssignableFrom(exception.getClass())) {
matchForRollback = true;
for (Class<? extends Exception> noRollbackExceptionClass : transactionMetadata.getNoRollbackFor()) {
if (noRollbackExceptionClass.isAssignableFrom(exception.getClass())) {
matchForNoRollback = true;
}
}
}
}
if (matchForRollback && !matchForNoRollback) {
if (transactionMetadata.getExceptionHandler() != null) {
ExceptionHandler exceptionHandler;
if (transactionMetadata.getResource() != null) {
exceptionHandler = injector.getInstance(Key.get(transactionMetadata.getExceptionHandler(), Names.named(transactionMetadata.getResource())));
} else {
exceptionHandler = injector.getInstance(transactionMetadata.getExceptionHandler());
}
if (exceptionHandler != null && exceptionHandler.handleException(exception, new TransactionMetadata().mergeFrom(transactionMetadata), currentTransaction)) {
transactionLogger.log("transaction exception has been handled", transactionLogger);
} else {
throw exception;
}
} else {
throw exception;
}
}
}
private TransactionMetadata readTransactionMetadata(MethodInvocation methodInvocation) {
Method method = methodInvocation.getMethod();
TransactionMetadata transactionMetadataDefaults = injector.getInstance(TransactionMetadata.class).defaults();
TransactionMetadata transactionMetadata = injector.getInstance(TransactionMetadata.class).defaults();
for (TransactionMetadataResolver transactionMetadataResolver : transactionMetadataResolvers) {
transactionMetadata.mergeFrom(transactionMetadataResolver.resolve(methodInvocation, transactionMetadataDefaults));
}
transactionMetadata.mergeFrom(deepGetAnnotation(method));
return transactionMetadata;
}
private <T extends TransactionHandler> T getTransactionHandler(Class<T> handlerClass, String resource) {
if (handlerClass == null) {
throw SeedException.createNew(TransactionErrorCode.NO_TRANSACTION_HANDLER_SPECIFIED);
}
try {
if (resource == null) {
return injector.getInstance(handlerClass);
} else {
return injector.getInstance(Key.get(handlerClass, Names.named(resource)));
}
} catch (Exception e) {
throw SeedException.wrap(e, TransactionErrorCode.SPECIFIED_TRANSACTION_HANDLER_NOT_FOUND)
.put("handlerClass", handlerClass.getSimpleName())
.put("resource", resource == null ? "default" : resource);
}
}
private Transactional deepGetAnnotation(final Method method) {
Optional<Transactional> transactional = TransactionalResolver.INSTANCE.apply(method);
return transactional.isPresent() ? transactional.get() : null;
}
}