/* * Copyright 2010-2017 Norwegian Agency for Public Management and eGovernment (Difi) * * Licensed under the EUPL, Version 1.1 or – as soon they * will be approved by the European Commission - subsequent * versions of the EUPL (the "Licence"); * * You may not use this work except in compliance with the Licence. * * You may obtain a copy of the Licence at: * * https://joinup.ec.europa.eu/community/eupl/og_page/eupl * * Unless required by applicable law or agreed to in * writing, software distributed under the Licence is * distributed on an "AS IS" basis, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. * See the Licence for the specific language governing * permissions and limitations under the Licence. */ package no.difi.oxalis.persistence.aop; import com.google.inject.Inject; import no.difi.oxalis.persistence.api.JdbcTxManager; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; /** * Looks for all @Transactional annotations and injects code for starting and stopping transactions. * <p> * User: andy * Date: 8/13/12 * Time: 2:19 PM */ public class TransactionalMethodInterceptor implements MethodInterceptor { @Inject private JdbcTxManager jdbcTxManager; /** * Starts a jdbc transaction if a transaction doesnt already exist. * Joins the transaction if one exists. * * @param invocation the method invocation joinpoint * @return the result of the call to {@link * org.aopalliance.intercept.Joinpoint#proceed()}, might be intercepted by the * interceptor. * @throws Throwable if the interceptors or the * target-object throws an exception. * IllegalStateException if there already exists a connection which is not transactional */ @Override public Object invoke(MethodInvocation invocation) throws Throwable { //we need to find out whether or not there is an existing transaction or an existing Connection final boolean transaction = jdbcTxManager.isTransaction(); final boolean connection = jdbcTxManager.isConnection(); //If there is a transaction running do nothing as the transaction will be cleaned up by the //code which created the transaction if (transaction) { //tx already exists so continues operation. jdbcTxManager.trace(String.format("Transaction already exists so not starting a new one when calling method: %s", invocation.getMethod().getName())); return invocation.proceed(); } //If there is a connection we have decided that this is an error because it means that //a non transactional method in a repository is calling a transactional method elsewhere. //which we believe is BAD DESIGN. (It would be possible to implement using a separate variable //for the transactional connection if we ever change our minds ;)) if (connection) { throw new IllegalStateException("Unable to start a transaction, there already exists a connection which is not transactional" + invocation.getMethod().getName()); } try { // Starts the transaction by setting the autocommit value to be false on the connection. jdbcTxManager.newConnection(false); jdbcTxManager.trace("Started new transaction due to annotation on method: " + invocation.getMethod().getName()); //makes the call to the method that is being wrapped. Object returnValue = invocation.proceed(); // Tries to commit the transaction. // it is still possible that the TxManager will rollback the transaction, // but as far as we are concerned our code worked as expected jdbcTxManager.commit(); //returns the result of the wrapped method call return returnValue; } catch (Throwable thr) { //if an exception is thrown we need to rollback the transaction. jdbcTxManager.trace("Rolling back transaction due to exception: " + thr.getMessage()); jdbcTxManager.rollback(); jdbcTxManager.trace("Rolling back transaction ok"); //rethrows the exception so that it can be handled by the calling code throw thr; } finally { //Essential that we clean up as we are placing connections on thread local jdbcTxManager.cleanUp(); } } }