1 package com.atlassian.xwork.interceptors;
2
3 import org.springframework.transaction.TransactionException;
4 import org.springframework.transaction.interceptor.TransactionAttribute;
5 import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
6 import org.springframework.transaction.PlatformTransactionManager;
7 import org.springframework.transaction.TransactionStatus;
8 import org.apache.log4j.Logger;
9 import com.opensymphony.xwork.ActionInvocation;
10 import com.opensymphony.xwork.ActionProxy;
11 import com.opensymphony.xwork.interceptor.PreResultListener;
12 import com.atlassian.util.profiling.ProfilingUtils;
13
14
15
16
17
18 class TransactionalInvocation
19 {
20 private static final Logger log = Logger.getLogger(TransactionalInvocation.class);
21
22 private final TransactionAttribute transactionAttribute = new DefaultTransactionAttribute(TransactionAttribute.PROPAGATION_REQUIRED);
23
24 static final ThreadLocal currentTransactionThreadLocal = new ThreadLocal();
25 private final PlatformTransactionManager transactionManager;
26
27 private TransactionStatus transactionStatus;
28
29 public TransactionalInvocation(PlatformTransactionManager transactionManager)
30 {
31 this.transactionManager = transactionManager;
32 }
33
34 public String invokeInTransaction(final ActionInvocation invocation) throws Exception
35 {
36 if (log.isDebugEnabled())
37 log.debug("Creating transaction for action invocation: " + getDetails(invocation));
38
39 TransactionStatus oldTransactionStatus = (TransactionStatus) currentTransactionThreadLocal.get();
40 setTransactionStatus(getNewTransaction());
41
42
43
44
45 invocation.addPreResultListener(new PreResultListener()
46 {
47 public void beforeResult(ActionInvocation actionInvocation, String s)
48 {
49 commitOrRollbackTransaction(invocation, false);
50
51 if (log.isDebugEnabled())
52 log.debug("Creating transaction for action result: " + getDetails(invocation));
53
54 setTransactionStatus(getNewTransaction());
55 }
56 });
57
58 boolean swallowCommitErrors = true;
59 try
60 {
61 String result = invokeAndHandleExceptions(invocation);
62 swallowCommitErrors = false;
63 return result;
64 }
65 finally
66 {
67 commitOrRollbackTransaction(invocation, swallowCommitErrors);
68 currentTransactionThreadLocal.set(oldTransactionStatus);
69 }
70 }
71
72 private String invokeAndHandleExceptions(ActionInvocation invocation) throws Exception
73 {
74 try
75 {
76 return invocation.invoke();
77 }
78 catch (Exception ex)
79 {
80 handleInvocationException(invocation, transactionAttribute, transactionStatus, ex);
81 throw ex;
82 }
83 }
84
85 private void commitOrRollbackTransaction(ActionInvocation actionInvocation, boolean swallowCommitErrors)
86 {
87 try
88 {
89
90
91 if (transactionStatus.isCompleted())
92 {
93 log.error("Action " + getDetails(actionInvocation) + " is already completed and can not be committed again.");
94 }
95 else if (transactionStatus.isRollbackOnly())
96 {
97 if (log.isDebugEnabled())
98 log.debug("Transaction status for action " + getDetails(actionInvocation) + " set to rollback only. Invoking rollback()");
99
100 transactionManager.rollback(transactionStatus);
101 }
102 else
103 {
104 if (log.isDebugEnabled())
105 log.debug("Committing transaction for action " + getDetails(actionInvocation));
106
107 transactionManager.commit(transactionStatus);
108 }
109 }
110 catch (RuntimeException e)
111 {
112 if (swallowCommitErrors)
113 {
114 log.error("Commit/Rollback exception occurred but was swallowed", e);
115 }
116 else
117 {
118 throw e;
119 }
120 }
121 }
122
123 private void handleInvocationException(ActionInvocation invocation, TransactionAttribute txAtt, TransactionStatus status, Throwable ex)
124 {
125 if (status == null)
126 return;
127
128 if (txAtt.rollbackOn(ex))
129 {
130 log.info("Invoking rollback for transaction on action '" + getDetails(invocation) + "' due to throwable: " + ex, ex);
131 status.setRollbackOnly();
132 }
133 else if (log.isDebugEnabled())
134 {
135 log.debug("Action " + getDetails(invocation) + " threw exception " + ex + " but did not trigger a rollback.");
136 }
137 }
138
139 private TransactionStatus getNewTransaction()
140 {
141 return transactionManager.getTransaction(transactionAttribute);
142 }
143
144 private void setTransactionStatus(TransactionStatus transactionStatus)
145 {
146 this.transactionStatus = transactionStatus;
147 currentTransactionThreadLocal.set(transactionStatus);
148 }
149
150 private String getDetails(ActionInvocation invocation)
151 {
152 ActionProxy proxy = invocation.getProxy();
153 String methodName = proxy.getConfig().getMethodName();
154
155 if (methodName == null)
156 methodName = "execute";
157
158 String actionClazz = ProfilingUtils.getJustClassName(proxy.getConfig().getClassName());
159
160 return proxy.getNamespace() + "/" + proxy.getActionName() + ".action (" + actionClazz + "." + methodName + "())";
161 }
162
163 }