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