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