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