KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > alfresco > util > transaction > SpringAwareUserTransaction


1 /*
2  * Copyright (C) 2005 Alfresco, Inc.
3  *
4  * Licensed under the Mozilla Public License version 1.1
5  * with a permitted attribution clause. You may obtain a
6  * copy of the License at
7  *
8  * http://www.alfresco.org/legal/license.txt
9  *
10  * Unless required by applicable law or agreed to in writing,
11  * software distributed under the License is distributed on an
12  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13  * either express or implied. See the License for the specific
14  * language governing permissions and limitations under the
15  * License.
16  */

17 package org.alfresco.util.transaction;
18
19 import java.lang.reflect.Method JavaDoc;
20
21 import javax.transaction.HeuristicMixedException JavaDoc;
22 import javax.transaction.HeuristicRollbackException JavaDoc;
23 import javax.transaction.NotSupportedException JavaDoc;
24 import javax.transaction.RollbackException JavaDoc;
25 import javax.transaction.Status JavaDoc;
26 import javax.transaction.SystemException JavaDoc;
27 import javax.transaction.UserTransaction JavaDoc;
28
29 import org.alfresco.error.StackTraceUtil;
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32 import org.springframework.transaction.NoTransactionException;
33 import org.springframework.transaction.PlatformTransactionManager;
34 import org.springframework.transaction.TransactionDefinition;
35 import org.springframework.transaction.TransactionStatus;
36 import org.springframework.transaction.interceptor.TransactionAspectSupport;
37 import org.springframework.transaction.interceptor.TransactionAttribute;
38 import org.springframework.transaction.interceptor.TransactionAttributeSource;
39
40 /**
41  * A <code>UserTransaction</code> that will allow the thread using it to participate
42  * in transactions that are normally only begun and committed by the <b>SpringFramework</b>
43  * transaction aware components.
44  * <p>
45  * This class is thread-safe in that it will detect multithreaded access and throw
46  * exceptions. Therefore </b>do not use on multiple threads</b>. Instances should be
47  * used only for the duration of the required user transaction and then discarded.
48  * Any attempt to reuse an instance will result in failure.
49  * <p>
50  * Nested user transaction are allowed.
51  *
52  * @see org.springframework.transaction.PlatformTransactionManager
53  * @see org.springframework.transaction.support.DefaultTransactionDefinition
54  *
55  * @author Derek Hulley
56  */

57 public class SpringAwareUserTransaction
58         extends TransactionAspectSupport
59         implements UserTransaction JavaDoc, TransactionAttributeSource, TransactionAttribute
60 {
61     /*
62      * There is some extra work in here to perform safety checks against the thread ID.
63      * This is because this class doesn't operate in an environment that guarantees that the
64      * thread coming into the begin() method is the same as the thread forcing commit() or
65      * rollback().
66      */

67     
68     private static final long serialVersionUID = 3762538897183224373L;
69
70     private static final String JavaDoc NAME = "UserTransaction";
71     
72     private static final Log logger = LogFactory.getLog(SpringAwareUserTransaction.class);
73     private static final Log traceLogger = LogFactory.getLog(SpringAwareUserTransaction.class.getName() + ".trace");
74     static
75     {
76         if (traceLogger.isDebugEnabled())
77         {
78             traceLogger.warn("Trace logging is enabled and will affect performance");
79         }
80     }
81
82     /** stores the begin() call stack trace when DEBUG is on for tracing */
83     private StackTraceElement JavaDoc[] traceDebugBeginTrace;
84     
85     private boolean readOnly;
86     private int isolationLevel;
87     private int propagationBehaviour;
88     private int timeout;
89     
90     /** Stores the user transaction current status as affected by explicit operations */
91     private int internalStatus = Status.STATUS_NO_TRANSACTION;
92     /** the transaction information used to check for mismatched begin/end */
93     private TransactionInfo internalTxnInfo;
94     /** keep the thread that the transaction was started on to perform thread safety checks */
95     private long threadId = Long.MIN_VALUE;
96     /** make sure that we clean up the thread transaction stack properly */
97     private boolean finalized = false;
98     
99     /**
100      * Creates a user transaction that defaults to {@link TransactionDefinition#PROPAGATION_REQUIRED}.
101      *
102      * @param transactionManager the transaction manager to use
103      * @param readOnly true to force a read-only transaction
104      * @param isolationLevel one of the
105      * {@link TransactionDefinition#ISOLATION_DEFAULT TransactionDefinition.ISOLATION_XXX}
106      * constants
107      * @param propagationBehaviour one of the
108      * {@link TransactionDefinition#PROPAGATION_MANDATORY TransactionDefinition.PROPAGATION__XXX}
109      * constants
110      * @param timeout the transaction timeout in seconds.
111      *
112      * @see TransactionDefinition#getTimeout()
113      */

114     public SpringAwareUserTransaction(
115             PlatformTransactionManager transactionManager,
116             boolean readOnly,
117             int isolationLevel,
118             int propagationBehaviour,
119             int timeout)
120     {
121         super();
122         setTransactionManager(transactionManager);
123         setTransactionAttributeSource(this);
124         this.readOnly = readOnly;
125         this.isolationLevel = isolationLevel;
126         this.propagationBehaviour = propagationBehaviour;
127         this.timeout = timeout;
128     }
129     
130     /**
131      * This class carries all the information required to fullfil requests about the transaction
132      * attributes. It acts as a source of the transaction attributes.
133      *
134      * @return Return <code>this</code> instance
135      */

136     public TransactionAttribute getTransactionAttribute(Method JavaDoc method, Class JavaDoc targetClass)
137     {
138         return this;
139     }
140     
141     /**
142      * The {@link UserTransaction } must rollback regardless of the error. The
143      * {@link #rollback() rollback} behaviour is implemented by simulating a caught
144      * exception. As this method will always return <code>true</code>, the rollback
145      * behaviour will be to rollback the transaction or mark it for rollback.
146      *
147      * @return Returns true always
148      */

149     public boolean rollbackOn(Throwable JavaDoc ex)
150     {
151         return true;
152     }
153     
154     /**
155      * @see #NAME
156      */

157     public String JavaDoc getName()
158     {
159         return NAME;
160     }
161
162     public boolean isReadOnly()
163     {
164         return readOnly;
165     }
166
167     public int getIsolationLevel()
168     {
169         return isolationLevel;
170     }
171
172     public int getPropagationBehavior()
173     {
174         return propagationBehaviour;
175     }
176
177     public int getTimeout()
178     {
179         return timeout;
180     }
181
182     /**
183      * Implementation required for {@link UserTransaction}.
184      */

185     public void setTransactionTimeout(int timeout) throws SystemException JavaDoc
186     {
187         if (internalStatus != Status.STATUS_NO_TRANSACTION)
188         {
189             throw new RuntimeException JavaDoc("Can only set the timeout before begin");
190         }
191         this.timeout = timeout;
192     }
193
194     /**
195      * Gets the current transaction info, or null if none exists.
196      * <p>
197      * A check is done to ensure that the transaction info on the stack is exactly
198      * the same instance used when this transaction was started.
199      * The internal status is also checked against the transaction info.
200      * These checks ensure that the transaction demarcation is done correctly and that
201      * thread safety is adhered to.
202      *
203      * @return Returns the current transaction
204      */

205     private TransactionInfo getTransactionInfo()
206     {
207         // a few quick self-checks
208
if (threadId < 0 && internalStatus != Status.STATUS_NO_TRANSACTION)
209         {
210             throw new RuntimeException JavaDoc("Transaction has been started but there is no thread ID");
211         }
212         else if (threadId >= 0 && internalStatus == Status.STATUS_NO_TRANSACTION)
213         {
214             throw new RuntimeException JavaDoc("Transaction has not been started but a thread ID has been recorded");
215         }
216         
217         TransactionInfo txnInfo = null;
218         try
219         {
220             txnInfo = TransactionAspectSupport.currentTransactionInfo();
221             // we are in a transaction
222
}
223         catch (NoTransactionException e)
224         {
225             // No transaction. It is possible that the transaction threw an exception during commit.
226
}
227         // perform checks for active transactions
228
if (internalStatus == Status.STATUS_ACTIVE)
229         {
230             if (Thread.currentThread().getId() != threadId)
231             {
232                 // the internally stored transaction info (retrieved in begin()) should match the info
233
// on the thread
234
throw new RuntimeException JavaDoc("UserTransaction may not be accessed by multiple threads");
235             }
236             else if (txnInfo == null)
237             {
238                 // internally we recorded a transaction starting, but there is nothing on the thread
239
throw new RuntimeException JavaDoc("Transaction boundaries have been made to overlap in the stack");
240             }
241             else if (txnInfo != internalTxnInfo)
242             {
243                 // the transaction info on the stack isn't the one we started with
244
throw new RuntimeException JavaDoc("UserTransaction begin/commit mismatch");
245             }
246         }
247         return txnInfo;
248     }
249
250     /**
251      * This status is a combination of the internal status, as recorded during explicit operations,
252      * and the status provided by the Spring support.
253      *
254      * @see Status
255      */

256     public synchronized int getStatus() throws SystemException JavaDoc
257     {
258         TransactionInfo txnInfo = getTransactionInfo();
259         
260         // if the txn info is null, then we are outside a transaction
261
if (txnInfo == null)
262         {
263             return internalStatus; // this is checked in getTransactionInfo
264
}
265
266         // normally the internal status is correct, but we only need to double check
267
// for the case where the transaction was marked for rollback, or rolledback
268
// in a deeper transaction
269
TransactionStatus txnStatus = txnInfo.getTransactionStatus();
270         if (internalStatus == Status.STATUS_ROLLEDBACK)
271         {
272             // explicitly rolled back at some point
273
return internalStatus;
274         }
275         else if (txnStatus.isRollbackOnly())
276         {
277             // marked for rollback at some point in the stack
278
return Status.STATUS_MARKED_ROLLBACK;
279         }
280         else
281         {
282             // just rely on the internal status
283
return internalStatus;
284         }
285     }
286
287     public synchronized void setRollbackOnly() throws IllegalStateException JavaDoc, SystemException JavaDoc
288     {
289         // just a check
290
TransactionInfo txnInfo = getTransactionInfo();
291
292         int status = getStatus();
293         // check the status
294
if (status == Status.STATUS_MARKED_ROLLBACK)
295         {
296             // this is acceptable
297
}
298         else if (status == Status.STATUS_NO_TRANSACTION)
299         {
300             throw new IllegalStateException JavaDoc("The transaction has not been started yet");
301         }
302         else if (status == Status.STATUS_ROLLING_BACK || status == Status.STATUS_ROLLEDBACK)
303         {
304             throw new IllegalStateException JavaDoc("The transaction has already been rolled back");
305         }
306         else if (status == Status.STATUS_COMMITTING || status == Status.STATUS_COMMITTED)
307         {
308             throw new IllegalStateException JavaDoc("The transaction has already been committed");
309         }
310         else if (status != Status.STATUS_ACTIVE)
311         {
312             throw new IllegalStateException JavaDoc("The transaction is not active: " + status);
313         }
314
315         // mark for rollback
316
txnInfo.getTransactionStatus().setRollbackOnly();
317         // make sure that we record the fact that we have been marked for rollback
318
internalStatus = Status.STATUS_MARKED_ROLLBACK;
319         // done
320
if (logger.isDebugEnabled())
321         {
322             logger.debug("Set transaction status to rollback only: " + this);
323         }
324     }
325     
326     /**
327      * @throws NotSupportedException if an attempt is made to reuse this instance
328      */

329     public synchronized void begin() throws NotSupportedException JavaDoc, SystemException JavaDoc
330     {
331         if (traceLogger.isDebugEnabled())
332         {
333             // get the stack trace
334
Exception JavaDoc e = new Exception JavaDoc();
335             e.fillInStackTrace();
336             traceDebugBeginTrace = e.getStackTrace();
337         }
338         
339         // make sure that the status and info align - the result may or may not be null
340
TransactionInfo txnInfo = getTransactionInfo();
341         if (internalStatus != Status.STATUS_NO_TRANSACTION)
342         {
343             throw new NotSupportedException JavaDoc("The UserTransaction may not be reused");
344         }
345         
346         // begin a transaction
347
internalTxnInfo = createTransactionIfNecessary(null, null); // super class will just pass nulls back to us
348
internalStatus = Status.STATUS_ACTIVE;
349         threadId = Thread.currentThread().getId();
350         
351         // done
352
if (logger.isDebugEnabled())
353         {
354             logger.debug("Began user transaction: " + this);
355         }
356     }
357     
358     /**
359      * @throws IllegalStateException if a transaction was not started
360      */

361     public synchronized void commit()
362             throws RollbackException JavaDoc, HeuristicMixedException JavaDoc, HeuristicRollbackException JavaDoc,
363             SecurityException JavaDoc, IllegalStateException JavaDoc, SystemException JavaDoc
364     {
365         // perform checks
366
TransactionInfo txnInfo = getTransactionInfo();
367
368         int status = getStatus();
369         // check the status
370
if (status == Status.STATUS_NO_TRANSACTION)
371         {
372             throw new IllegalStateException JavaDoc("The transaction has not yet begun");
373         }
374         else if (status == Status.STATUS_ROLLING_BACK || status == Status.STATUS_ROLLEDBACK)
375         {
376             throw new RollbackException JavaDoc("The transaction has already been rolled back");
377         }
378         else if (status == Status.STATUS_MARKED_ROLLBACK)
379         {
380             throw new RollbackException JavaDoc("The transaction has already been marked for rollback");
381         }
382         else if (status == Status.STATUS_COMMITTING || status == Status.STATUS_COMMITTED)
383         {
384             throw new IllegalStateException JavaDoc("The transaction has already been committed");
385         }
386         else if (status != Status.STATUS_ACTIVE || txnInfo == null)
387         {
388             throw new IllegalStateException JavaDoc("No user transaction is active");
389         }
390             
391         if (!finalized)
392         {
393             try
394             {
395                 // the status seems correct - we can try a commit
396
doCommitTransactionAfterReturning(txnInfo);
397             }
398             catch (Throwable JavaDoc e)
399             {
400                 logger.error("Transaction didn't commit", e);
401                 // commit failed
402
internalStatus = Status.STATUS_ROLLEDBACK;
403                 throw new RollbackException JavaDoc("Transaction didn't commit: " + e.getMessage());
404             }
405             finally
406             {
407                 // make sure that we clean up the stack
408
doFinally(txnInfo);
409                 finalized = true;
410                 // clean up debug
411
if (traceLogger.isDebugEnabled())
412                 {
413                     traceDebugBeginTrace = null;
414                 }
415             }
416         }
417         
418         // regardless of whether the transaction was finally committed or not, the status
419
// as far as UserTransaction is concerned should be 'committed'
420

421         // keep track that this UserTransaction was explicitly committed
422
internalStatus = Status.STATUS_COMMITTED;
423         
424         // done
425
if (logger.isDebugEnabled())
426         {
427             logger.debug("Committed user transaction: " + this);
428         }
429     }
430
431     public synchronized void rollback()
432             throws IllegalStateException JavaDoc, SecurityException JavaDoc, SystemException JavaDoc
433     {
434         // perform checks
435
TransactionInfo txnInfo = getTransactionInfo();
436         
437         int status = getStatus();
438         // check the status
439
if (status == Status.STATUS_ROLLING_BACK || status == Status.STATUS_ROLLEDBACK)
440         {
441             throw new IllegalStateException JavaDoc("The transaction has already been rolled back");
442         }
443         else if (status == Status.STATUS_COMMITTING || status == Status.STATUS_COMMITTED)
444         {
445             throw new IllegalStateException JavaDoc("The transaction has already been committed");
446         }
447         else if (txnInfo == null)
448         {
449             throw new IllegalStateException JavaDoc("No user transaction is active");
450         }
451     
452         if (!finalized)
453         {
454             try
455             {
456                 // force a rollback by generating an exception that will trigger a rollback
457
doCloseTransactionAfterThrowing(txnInfo, new Exception JavaDoc());
458             }
459             finally
460             {
461                 // make sure that we clean up the stack
462
doFinally(txnInfo);
463                 finalized = true;
464                 // clean up debug
465
if (traceLogger.isDebugEnabled())
466                 {
467                     traceDebugBeginTrace = null;
468                 }
469             }
470         }
471
472         // the internal status notes that we were specifically rolled back
473
internalStatus = Status.STATUS_ROLLEDBACK;
474         
475         // done
476
if (logger.isDebugEnabled())
477         {
478             logger.debug("Rolled back user transaction: " + this);
479         }
480     }
481     
482     @Override JavaDoc
483     protected String JavaDoc methodIdentification(Method JavaDoc method)
484     {
485         // note: override for debugging purposes - this method called by Spring
486
return NAME;
487     }
488     
489     @Override JavaDoc
490     protected void finalize() throws Throwable JavaDoc
491     {
492         if (traceLogger.isDebugEnabled() && traceDebugBeginTrace != null)
493         {
494             StringBuilder JavaDoc sb = new StringBuilder JavaDoc(1024);
495             StackTraceUtil.buildStackTrace(
496                     "UserTransaction being garbage collected without a commit() or rollback().",
497                     traceDebugBeginTrace,
498                     sb,
499                     -1);
500             traceLogger.error(sb);
501         }
502     }
503 }
504
Popular Tags