• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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@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 
invoke(MethodInvocation methodInvocation)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.
readTransactionMetadata(MethodInvocation methodInvocation)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    */
rollbackIfNecessary(Transactional transactional, Exception e, EntityTransaction txn)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