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