KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > ejb > plugins > TxInterceptorCMT


1 /*
2 * JBoss, Home of Professional Open Source
3 * Copyright 2005, JBoss Inc., and individual contributors as indicated
4 * by the @authors tag. See the copyright.txt in the distribution for a
5 * full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */

22 package org.jboss.ejb.plugins;
23
24 import org.jboss.invocation.Invocation;
25 import org.jboss.invocation.InvocationType;
26 import org.jboss.metadata.BeanMetaData;
27 import org.jboss.metadata.MetaData;
28 import org.jboss.metadata.XmlLoadable;
29 import org.jboss.tm.JBossTransactionRolledbackException;
30 import org.jboss.tm.JBossTransactionRolledbackLocalException;
31 import org.jboss.tm.TransactionTimeoutConfiguration;
32 import org.jboss.util.NestedException;
33 import org.jboss.util.deadlock.ApplicationDeadlockException;
34 import org.w3c.dom.Element JavaDoc;
35
36 import javax.ejb.EJBException JavaDoc;
37 import javax.ejb.TransactionRequiredLocalException JavaDoc;
38 import javax.transaction.HeuristicMixedException JavaDoc;
39 import javax.transaction.HeuristicRollbackException JavaDoc;
40 import javax.transaction.RollbackException JavaDoc;
41 import javax.transaction.Status JavaDoc;
42 import javax.transaction.SystemException JavaDoc;
43 import javax.transaction.Transaction JavaDoc;
44 import javax.transaction.TransactionRequiredException JavaDoc;
45 import javax.transaction.TransactionRolledbackException JavaDoc;
46 import java.lang.reflect.Method JavaDoc;
47 import java.rmi.RemoteException JavaDoc;
48 import java.util.HashMap JavaDoc;
49 import java.util.Map JavaDoc;
50 import java.util.Random JavaDoc;
51 import java.util.Iterator JavaDoc;
52 import java.util.ArrayList JavaDoc;
53
54 /**
55  * This interceptor handles transactions for CMT beans.
56  *
57  * @author <a HREF="mailto:rickard.oberg@telkel.com">Rickard �berg</a>
58  * @author <a HREF="mailto:marc.fleury@telkel.com">Marc Fleury</a>
59  * @author <a HREF="mailto:sebastien.alborini@m4x.org">Sebastien Alborini</a>
60  * @author <a HREF="mailto:akkerman@cs.nyu.edu">Anatoly Akkerman</a>
61  * @author <a HREF="mailto:osh@sparre.dk">Ole Husgaard</a>
62  * @author <a HREF="mailto:bill@jboss.org">Bill Burke</a>
63  * @version $Revision: 37459 $
64  */

65 public class TxInterceptorCMT extends AbstractTxInterceptor implements XmlLoadable
66 {
67
68    // Constants -----------------------------------------------------
69

70
71    public static int MAX_RETRIES = 5;
72    public static Random JavaDoc random = new Random JavaDoc();
73
74    // Attributes ----------------------------------------------------
75

76    /**
77     * Whether an exception should be thrown if the transaction is not
78     * active, even though the application doesn't throw an exception
79     */

80    private boolean exceptionRollback = true;
81    
82    private TxRetryExceptionHandler[] retryHandlers = null;
83
84    // Static --------------------------------------------------------
85

86
87    /**
88     * Detects exception contains is or a ApplicationDeadlockException.
89     */

90    public static ApplicationDeadlockException isADE(Throwable JavaDoc t)
91    {
92       while (t!=null)
93       {
94          if (t instanceof ApplicationDeadlockException)
95          {
96             return (ApplicationDeadlockException)t;
97          }
98          else if (t instanceof RemoteException JavaDoc)
99          {
100             t = ((RemoteException JavaDoc)t).detail;
101          }
102          else if (t instanceof EJBException JavaDoc)
103          {
104             t = ((EJBException JavaDoc)t).getCausedByException();
105          }
106          else
107          {
108             return null;
109          }
110       }
111       return null;
112    }
113    
114    // Constructors --------------------------------------------------
115

116    // Public --------------------------------------------------------
117

118    // XmlLoadable implementation ------------------------------------
119

120    public void importXml(Element JavaDoc ielement)
121    {
122       try
123       {
124          Element JavaDoc element = MetaData.getOptionalChild(ielement, "retry-handlers");
125          if (element == null) return;
126          ArrayList JavaDoc list = new ArrayList JavaDoc();
127          Iterator JavaDoc handlers = MetaData.getChildrenByTagName(element, "handler");
128          while (handlers.hasNext())
129          {
130             Element JavaDoc handler = (Element JavaDoc)handlers.next();
131             String JavaDoc className = MetaData.getElementContent(handler).trim();
132             Class JavaDoc clazz = SecurityActions.getContextClassLoader().loadClass(className);
133             list.add(clazz.newInstance());
134          }
135          retryHandlers = (TxRetryExceptionHandler[])list.toArray(new TxRetryExceptionHandler[list.size()]);
136       }
137       catch (Exception JavaDoc ex)
138       {
139          log.warn("Unable to importXml for the TxInterceptorCMT", ex);
140       }
141    }
142
143    // Interceptor implementation ------------------------------------
144

145    public void create() throws Exception JavaDoc
146    {
147       super.create();
148       BeanMetaData bmd = getContainer().getBeanMetaData();
149       exceptionRollback = bmd.getExceptionRollback();
150       if (exceptionRollback == false)
151          exceptionRollback = bmd.getApplicationMetaData().getExceptionRollback();
152    }
153
154    public Object JavaDoc invokeHome(Invocation invocation) throws Exception JavaDoc
155    {
156       Transaction JavaDoc oldTransaction = invocation.getTransaction();
157       for (int i = 0; i < MAX_RETRIES; i++)
158       {
159          try
160          {
161             return runWithTransactions(invocation);
162          }
163          catch (Exception JavaDoc ex)
164          {
165             checkRetryable(i, ex, oldTransaction);
166          }
167       }
168       throw new RuntimeException JavaDoc("Unreachable");
169    }
170
171    /**
172     * This method does invocation interpositioning of tx management
173     */

174    public Object JavaDoc invoke(Invocation invocation) throws Exception JavaDoc
175    {
176       Transaction JavaDoc oldTransaction = invocation.getTransaction();
177       for (int i = 0; i < MAX_RETRIES; i++)
178       {
179          try
180          {
181             return runWithTransactions(invocation);
182          }
183          catch (Exception JavaDoc ex)
184          {
185             checkRetryable(i, ex, oldTransaction);
186          }
187       }
188       throw new RuntimeException JavaDoc("Unreachable");
189    }
190
191    private void checkRetryable(int i, Exception JavaDoc ex, Transaction JavaDoc oldTransaction) throws Exception JavaDoc
192    {
193       // if oldTransaction != null this means tx was propagated over the wire
194
// and we cannot retry it
195
if (i + 1 >= MAX_RETRIES || oldTransaction != null) throw ex;
196       // Keep ADE check for backward compatibility
197
ApplicationDeadlockException deadlock = isADE(ex);
198       if (deadlock != null)
199       {
200          if (!deadlock.retryable()) throw deadlock;
201          log.debug(deadlock.getMessage() + " retrying tx " + (i + 1));
202       }
203       else if (retryHandlers != null)
204       {
205          boolean retryable = false;
206          for (int j = 0; j < retryHandlers.length; j++)
207          {
208             retryable = retryHandlers[j].retry(ex);
209             if (retryable) break;
210          }
211          if (!retryable) throw ex;
212          log.debug(ex.getMessage() + " retrying tx " + (i + 1));
213       }
214       else
215       {
216          throw ex;
217       }
218       Thread.sleep(random.nextInt(1 + i), random.nextInt(1000));
219    }
220
221    // Private ------------------------------------------------------
222

223    private void printMethod(Method JavaDoc m, byte type)
224    {
225       String JavaDoc txName;
226       switch(type)
227       {
228          case MetaData.TX_MANDATORY:
229             txName = "TX_MANDATORY";
230             break;
231          case MetaData.TX_NEVER:
232             txName = "TX_NEVER";
233             break;
234          case MetaData.TX_NOT_SUPPORTED:
235             txName = "TX_NOT_SUPPORTED";
236             break;
237          case MetaData.TX_REQUIRED:
238             txName = "TX_REQUIRED";
239             break;
240          case MetaData.TX_REQUIRES_NEW:
241             txName = "TX_REQUIRES_NEW";
242             break;
243          case MetaData.TX_SUPPORTS:
244             txName = "TX_SUPPORTS";
245             break;
246          default:
247             txName = "TX_UNKNOWN";
248       }
249
250       String JavaDoc methodName;
251       if(m != null)
252          methodName = m.getName();
253       else
254          methodName ="<no method>";
255
256       if (log.isTraceEnabled())
257       {
258          if (m != null && (type == MetaData.TX_REQUIRED || type == MetaData.TX_REQUIRES_NEW))
259             log.trace(txName + " for " + methodName + " timeout=" + container.getBeanMetaData().getTransactionTimeout(methodName));
260          else
261             log.trace(txName + " for " + methodName);
262       }
263    }
264
265     /*
266      * This method does invocation interpositioning of tx management.
267      *
268      * This is where the meat is. We define what to do with the Tx based
269      * on the declaration.
270      * The Invocation is always the final authority on what the Tx
271      * looks like leaving this interceptor. In other words, interceptors
272      * down the chain should not rely on the thread association with Tx but
273      * on the Tx present in the Invocation.
274      *
275      * @param remoteInvocation If <code>true</code> this is an invocation
276      * of a method in the remote interface, otherwise
277      * it is an invocation of a method in the home
278      * interface.
279      * @param invocation The <code>Invocation</code> of this call.
280      */

281    private Object JavaDoc runWithTransactions(Invocation invocation) throws Exception JavaDoc
282    {
283       // Old transaction is the transaction that comes with the MI
284
Transaction JavaDoc oldTransaction = invocation.getTransaction();
285       // New transaction is the new transaction this might start
286
Transaction JavaDoc newTransaction = null;
287
288       boolean trace = log.isTraceEnabled();
289       if( trace )
290          log.trace("Current transaction in MI is " + oldTransaction);
291
292       InvocationType type = invocation.getType();
293       byte transType = container.getBeanMetaData().getTransactionMethod(invocation.getMethod(), type);
294
295       if ( trace )
296          printMethod(invocation.getMethod(), transType);
297
298       // Thread arriving must be clean (jboss doesn't set the thread
299
// previously). However optimized calls come with associated
300
// thread for example. We suspend the thread association here, and
301
// resume in the finally block of the following try.
302
Transaction JavaDoc threadTx = tm.suspend();
303       if( trace )
304          log.trace("Thread came in with tx " + threadTx);
305       try
306       {
307          switch (transType)
308          {
309             case MetaData.TX_NOT_SUPPORTED:
310             {
311                // Do not set a transaction on the thread even if in MI, just run
312
try
313                {
314                   invocation.setTransaction(null);
315                   return invokeNext(invocation, false);
316                }
317                finally
318                {
319                   invocation.setTransaction(oldTransaction);
320                }
321             }
322             case MetaData.TX_REQUIRED:
323             {
324                int oldTimeout = 0;
325                Transaction JavaDoc theTransaction = oldTransaction;
326                if (oldTransaction == null)
327                { // No tx running
328
// Create tx
329
oldTimeout = startTransaction(invocation);
330
331                   // get the tx
332
newTransaction = tm.getTransaction();
333                   if( trace )
334                      log.trace("Starting new tx " + newTransaction);
335
336                   // Let the method invocation know
337
invocation.setTransaction(newTransaction);
338                   theTransaction = newTransaction;
339                }
340                else
341                {
342                   // We have a tx propagated
343
// Associate it with the thread
344
tm.resume(oldTransaction);
345                }
346
347                // Continue invocation
348
try
349                {
350                   Object JavaDoc result = invokeNext(invocation, oldTransaction != null);
351                   checkTransactionStatus(theTransaction, type);
352                   return result;
353                }
354                finally
355                {
356                   if( trace )
357                      log.trace("TxInterceptorCMT: In finally");
358
359                   // Only do something if we started the transaction
360
if (newTransaction != null)
361                      endTransaction(invocation, newTransaction, oldTransaction, oldTimeout);
362                   else
363                      tm.suspend();
364                }
365             }
366             case MetaData.TX_SUPPORTS:
367             {
368                // Associate old transaction with the thread
369
// Some TMs cannot resume a null transaction and will throw
370
// an exception (e.g. Tyrex), so make sure it is not null
371
if (oldTransaction != null)
372                {
373                   tm.resume(oldTransaction);
374                }
375
376                try
377                {
378                   Object JavaDoc result = invokeNext(invocation, oldTransaction != null);
379                   if (oldTransaction != null)
380                      checkTransactionStatus(oldTransaction, type);
381                   return result;
382                }
383                finally
384                {
385                   tm.suspend();
386                }
387
388                // Even on error we don't do anything with the tx,
389
// we didn't start it
390
}
391             case MetaData.TX_REQUIRES_NEW:
392             {
393                // Always begin a transaction
394
int oldTimeout = startTransaction(invocation);
395
396                // get it
397
newTransaction = tm.getTransaction();
398
399                // Set it on the method invocation
400
invocation.setTransaction(newTransaction);
401                // Continue invocation
402
try
403                {
404                   Object JavaDoc result = invokeNext(invocation, false);
405                   checkTransactionStatus(newTransaction, type);
406                   return result;
407                }
408                finally
409                {
410                   // We started the transaction for sure so we commit or roll back
411
endTransaction(invocation, newTransaction, oldTransaction, oldTimeout);
412                }
413             }
414             case MetaData.TX_MANDATORY:
415             {
416                if (oldTransaction == null)
417                {
418                   if (type == InvocationType.LOCAL ||
419                         type == InvocationType.LOCALHOME)
420                   {
421                      throw new TransactionRequiredLocalException JavaDoc(
422                            "Transaction Required");
423                   }
424                   else
425                   {
426                      throw new TransactionRequiredException JavaDoc(
427                            "Transaction Required");
428                   }
429                }
430
431                // Associate it with the thread
432
tm.resume(oldTransaction);
433                try
434                {
435                   Object JavaDoc result = invokeNext(invocation, true);
436                   checkTransactionStatus(oldTransaction, type);
437                   return result;
438                }
439                finally
440                {
441                   tm.suspend();
442                }
443             }
444             case MetaData.TX_NEVER:
445             {
446                if (oldTransaction != null)
447                {
448                   throw new EJBException JavaDoc("Transaction not allowed");
449                }
450                return invokeNext(invocation, false);
451             }
452             default:
453                 log.error("Unknown TX attribute "+transType+" for method"+invocation.getMethod());
454          }
455       }
456       finally
457       {
458          // IN case we had a Tx associated with the thread reassociate
459
if (threadTx != null)
460             tm.resume(threadTx);
461       }
462
463       return null;
464    }
465
466    private int startTransaction(final Invocation invocation) throws Exception JavaDoc
467    {
468       // Get the old timeout and set any new timeout
469
int oldTimeout = -1;
470       if (tm instanceof TransactionTimeoutConfiguration)
471       {
472          oldTimeout = ((TransactionTimeoutConfiguration) tm).getTransactionTimeout();
473          int newTimeout = container.getBeanMetaData().getTransactionTimeout(invocation.getMethod());
474          tm.setTransactionTimeout(newTimeout);
475       }
476       tm.begin();
477       return oldTimeout;
478    }
479
480    private void endTransaction(final Invocation invocation, final Transaction JavaDoc tx, final Transaction JavaDoc oldTx, final int oldTimeout)
481       throws TransactionRolledbackException, SystemException JavaDoc
482    {
483       // Assert the correct transaction association
484
Transaction JavaDoc current = tm.getTransaction();
485       if ((tx == null && current != null) || tx.equals(current) == false)
486          throw new IllegalStateException JavaDoc("Wrong transaction association: expected " + tx + " was " + current);
487
488       try
489       {
490          // Marked rollback
491
if (tx.getStatus() == Status.STATUS_MARKED_ROLLBACK)
492          {
493             tx.rollback();
494          }
495          else
496          {
497             // Commit tx
498
// This will happen if
499
// a) everything goes well
500
// b) app. exception was thrown
501
tx.commit();
502          }
503       }
504       catch (RollbackException JavaDoc e)
505       {
506          throwJBossException(e, invocation.getType());
507       }
508       catch (HeuristicMixedException JavaDoc e)
509       {
510          throwJBossException(e, invocation.getType());
511       }
512       catch (HeuristicRollbackException JavaDoc e)
513       {
514          throwJBossException(e, invocation.getType());
515       }
516       catch (SystemException JavaDoc e)
517       {
518          throwJBossException(e, invocation.getType());
519       }
520       finally
521       {
522          // reassociate the oldTransaction with the Invocation (even null)
523
invocation.setTransaction(oldTx);
524          // Always drop thread association even if committing or
525
// rolling back the newTransaction because not all TMs
526
// will drop thread associations when commit() or rollback()
527
// are called through tx itself (see JTA spec that seems to
528
// indicate that thread assoc is required to be dropped only
529
// when commit() and rollback() are called through TransactionManager
530
// interface)
531
//tx has committed, so we can't throw txRolledbackException.
532
tm.suspend();
533          // Reset the transaction timeout (unless we didn't set it)
534
if (oldTimeout != -1)
535             tm.setTransactionTimeout(oldTimeout);
536       }
537    }
538
539
540    // Protected ----------------------------------------------------
541

542    /**
543     * Rethrow the exception as a rollback or rollback local
544     *
545     * @param e the exception
546     * @param type the invocation type
547     */

548    protected void throwJBossException(Exception JavaDoc e, InvocationType type)
549       throws TransactionRolledbackException
550    {
551       // Unwrap a nested exception if possible. There is no
552
// point in the extra wrapping, and the EJB spec should have
553
// just used javax.transaction exceptions
554
if (e instanceof NestedException)
555          {
556             NestedException rollback = (NestedException) e;
557             if(rollback.getCause() instanceof Exception JavaDoc)
558             {
559                e = (Exception JavaDoc) rollback.getCause();
560             }
561          }
562          if (type == InvocationType.LOCAL
563              || type == InvocationType.LOCALHOME)
564          {
565             throw new JBossTransactionRolledbackLocalException(e);
566          }
567          else
568          {
569             throw new JBossTransactionRolledbackException(e);
570          }
571    }
572
573    /**
574     * The application has not thrown an exception, but...
575     * When exception-on-rollback is true,
576     * check whether the transaction is not active.
577     * If it did not throw an exception anyway.
578     *
579     * @param tx the transaction
580     * @param type the invocation type
581     * @throws TransactionRolledbackException if transaction is no longer active
582     */

583    protected void checkTransactionStatus(Transaction JavaDoc tx, InvocationType type)
584       throws TransactionRolledbackException
585    {
586       if (exceptionRollback)
587       {
588          if (log.isTraceEnabled())
589             log.trace("No exception from ejb, checking transaction status: " + tx);
590          int status = Status.STATUS_UNKNOWN;
591          try
592          {
593             status = tx.getStatus();
594          }
595          catch (Throwable JavaDoc t)
596          {
597             log.debug("Ignored error trying to retrieve transaction status", t);
598          }
599          if (status != Status.STATUS_ACTIVE)
600          {
601             Exception JavaDoc e = new Exception JavaDoc("Transaction cannot be committed (probably transaction timeout): " + tx);
602             throwJBossException(e, type);
603          }
604       }
605    }
606    
607    // Inner classes -------------------------------------------------
608

609    // Monitorable implementation ------------------------------------
610
public void sample(Object JavaDoc s)
611    {
612       // Just here to because Monitorable request it but will be removed soon
613
}
614    public Map JavaDoc retrieveStatistic()
615    {
616       return null;
617    }
618    public void resetStatistic()
619    {
620    }
621 }
622
Popular Tags