View Javadoc

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   * Invoke an XWork ActionInvocation within a transaction. If the invocation throws an unchecked exception, roll
15   * back the txn.
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      /** @deprecated since atlassian-xwork 1.1 - please remove once we've weeded out anyone who uses this (broken) functionality */
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          // Add listener to commit transaction between action and result. If we do not do this, we run the risk
42          // that if the result is a redirect, the user will follow the redirect before the transaction has been
43          // committed, and see stale data.
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          // If you try to commit a transaction that is marked for rollback, you'll get an UnexpectedRollbackException
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 }