Home | History | Annotate | Download | only in jpa
      1 /**
      2  * Copyright (C) 2010 Google, Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.inject.persist.jpa;
     18 
     19 import com.google.inject.Inject;
     20 import com.google.inject.persist.Transactional;
     21 import com.google.inject.persist.UnitOfWork;
     22 
     23 import org.aopalliance.intercept.MethodInterceptor;
     24 import org.aopalliance.intercept.MethodInvocation;
     25 
     26 import java.lang.reflect.Method;
     27 
     28 import javax.persistence.EntityManager;
     29 import javax.persistence.EntityTransaction;
     30 
     31 /**
     32  * @author Dhanji R. Prasanna (dhanji (at) gmail.com)
     33  */
     34 class JpaLocalTxnInterceptor implements MethodInterceptor {
     35 
     36   // TODO(gak): Move these args to the cxtor & make these final.
     37   @Inject
     38   private JpaPersistService emProvider = null;
     39 
     40   @Inject
     41   private UnitOfWork unitOfWork = null;
     42 
     43   @Transactional
     44   private static class Internal {}
     45 
     46   // Tracks if the unit of work was begun implicitly by this transaction.
     47   private final ThreadLocal<Boolean> didWeStartWork = new ThreadLocal<Boolean>();
     48 
     49   public Object invoke(MethodInvocation methodInvocation) throws Throwable {
     50 
     51     // Should we start a unit of work?
     52     if (!emProvider.isWorking()) {
     53       emProvider.begin();
     54       didWeStartWork.set(true);
     55     }
     56 
     57     Transactional transactional = readTransactionMetadata(methodInvocation);
     58     EntityManager em = this.emProvider.get();
     59 
     60     // Allow 'joining' of transactions if there is an enclosing @Transactional method.
     61     if (em.getTransaction().isActive()) {
     62       return methodInvocation.proceed();
     63     }
     64 
     65     final EntityTransaction txn = em.getTransaction();
     66     txn.begin();
     67 
     68     Object result;
     69     try {
     70       result = methodInvocation.proceed();
     71 
     72     } catch (Exception e) {
     73       //commit transaction only if rollback didnt occur
     74       if (rollbackIfNecessary(transactional, e, txn)) {
     75         txn.commit();
     76       }
     77 
     78       //propagate whatever exception is thrown anyway
     79       throw e;
     80     } finally {
     81       // Close the em if necessary (guarded so this code doesn't run unless catch fired).
     82       if (null != didWeStartWork.get() && !txn.isActive()) {
     83         didWeStartWork.remove();
     84         unitOfWork.end();
     85       }
     86     }
     87 
     88     //everything was normal so commit the txn (do not move into try block above as it
     89     //  interferes with the advised method's throwing semantics)
     90     try {
     91       txn.commit();
     92     } finally {
     93       //close the em if necessary
     94       if (null != didWeStartWork.get() ) {
     95         didWeStartWork.remove();
     96         unitOfWork.end();
     97       }
     98     }
     99 
    100     //or return result
    101     return result;
    102   }
    103 
    104   // TODO(dhanji): Cache this method's results.
    105   private Transactional readTransactionMetadata(MethodInvocation methodInvocation) {
    106     Transactional transactional;
    107     Method method = methodInvocation.getMethod();
    108     Class<?> targetClass = methodInvocation.getThis().getClass();
    109 
    110     transactional = method.getAnnotation(Transactional.class);
    111     if (null == transactional) {
    112       // If none on method, try the class.
    113       transactional = targetClass.getAnnotation(Transactional.class);
    114     }
    115     if (null == transactional) {
    116       // If there is no transactional annotation present, use the default
    117       transactional = Internal.class.getAnnotation(Transactional.class);
    118     }
    119 
    120     return transactional;
    121   }
    122 
    123   /**
    124    * Returns True if rollback DID NOT HAPPEN (i.e. if commit should continue).
    125    *
    126    * @param transactional The metadata annotaiton of the method
    127    * @param e The exception to test for rollback
    128    * @param txn A JPA Transaction to issue rollbacks on
    129    */
    130   private boolean rollbackIfNecessary(Transactional transactional, Exception e,
    131       EntityTransaction txn) {
    132     boolean commit = true;
    133 
    134     //check rollback clauses
    135     for (Class<? extends Exception> rollBackOn : transactional.rollbackOn()) {
    136 
    137       //if one matched, try to perform a rollback
    138       if (rollBackOn.isInstance(e)) {
    139         commit = false;
    140 
    141         //check ignore clauses (supercedes rollback clause)
    142         for (Class<? extends Exception> exceptOn : transactional.ignore()) {
    143           //An exception to the rollback clause was found, DON'T rollback
    144           // (i.e. commit and throw anyway)
    145           if (exceptOn.isInstance(e)) {
    146             commit = true;
    147             break;
    148           }
    149         }
    150 
    151         //rollback only if nothing matched the ignore check
    152         if (!commit) {
    153           txn.rollback();
    154         }
    155         //otherwise continue to commit
    156 
    157         break;
    158       }
    159     }
    160 
    161     return commit;
    162   }
    163 }
    164