View Javadoc

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