Save This Page
Home » jboss-5.0.0.CR1-src » org » jboss » ejb » plugins » [javadoc | source]
    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;
   35   
   36   import javax.ejb.EJBException;
   37   import javax.ejb.TransactionRequiredLocalException;
   38   import javax.transaction.HeuristicMixedException;
   39   import javax.transaction.HeuristicRollbackException;
   40   import javax.transaction.RollbackException;
   41   import javax.transaction.Status;
   42   import javax.transaction.SystemException;
   43   import javax.transaction.Transaction;
   44   import javax.transaction.TransactionRequiredException;
   45   import javax.transaction.TransactionRolledbackException;
   46   import java.lang.reflect.Method;
   47   import java.rmi.RemoteException;
   48   import java.util.HashMap;
   49   import java.util.Map;
   50   import java.util.Random;
   51   import java.util.Iterator;
   52   import java.util.ArrayList;
   53   
   54   /**
   55    *  This interceptor handles transactions for CMT beans.
   56    *
   57    *  @author <a href="mailto:rickard.oberg@telkel.com">Rickard Oberg</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: 66439 $
   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 random = new Random();
   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 t)
   91      {
   92         while (t!=null)
   93         {
   94            if (t instanceof ApplicationDeadlockException)
   95            {
   96               return (ApplicationDeadlockException)t;
   97            }
   98            else if (t instanceof RemoteException)
   99            {
  100               t = ((RemoteException)t).detail;
  101            }
  102            else if (t instanceof EJBException)
  103            {
  104               t = ((EJBException)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 ielement)
  121      {
  122         try
  123         {
  124            Element element = MetaData.getOptionalChild(ielement, "retry-handlers");
  125            if (element == null) return;
  126            ArrayList list = new ArrayList();
  127            Iterator handlers = MetaData.getChildrenByTagName(element, "handler");
  128            while (handlers.hasNext())
  129            {
  130               Element handler = (Element)handlers.next();
  131               String className = MetaData.getElementContent(handler).trim();
  132               Class clazz = SecurityActions.getContextClassLoader().loadClass(className);
  133               list.add(clazz.newInstance());
  134            }
  135            retryHandlers = (TxRetryExceptionHandler[])list.toArray(new TxRetryExceptionHandler[list.size()]);
  136         }
  137         catch (Exception 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
  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 invokeHome(Invocation invocation) throws Exception
  155      {
  156         Transaction oldTransaction = invocation.getTransaction();
  157         for (int i = 0; i < MAX_RETRIES; i++)
  158         {
  159            try
  160            {
  161               return runWithTransactions(invocation);
  162            }
  163            catch (Exception ex)
  164            {
  165               checkRetryable(i, ex, oldTransaction);
  166            }
  167         }
  168         throw new RuntimeException("Unreachable");
  169      }
  170   
  171      /**
  172       *  This method does invocation interpositioning of tx management
  173       */
  174      public Object invoke(Invocation invocation) throws Exception
  175      {
  176         Transaction oldTransaction = invocation.getTransaction();
  177         for (int i = 0; i < MAX_RETRIES; i++)
  178         {
  179            try
  180            {
  181               return runWithTransactions(invocation);
  182            }
  183            catch (Exception ex)
  184            {
  185               checkRetryable(i, ex, oldTransaction);
  186            }
  187         }
  188         throw new RuntimeException("Unreachable");
  189      }
  190   
  191      private void checkRetryable(int i, Exception ex, Transaction oldTransaction) throws Exception
  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 m, byte type)
  224      {
  225         String 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 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 runWithTransactions(Invocation invocation) throws Exception
  282      {
  283         // Old transaction is the transaction that comes with the MI
  284         Transaction oldTransaction = invocation.getTransaction();
  285         // New transaction is the new transaction this might start
  286         Transaction 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 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 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 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 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 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(
  422                              "Transaction Required");
  423                     }
  424                     else
  425                     {
  426                        throw new TransactionRequiredException(
  427                              "Transaction Required");
  428                     }
  429                  }
  430   
  431                  // Associate it with the thread
  432                  tm.resume(oldTransaction);
  433                  try
  434                  {
  435                     Object 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("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
  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 tx, final Transaction oldTx, final int oldTimeout) 
  481         throws TransactionRolledbackException, SystemException
  482      {
  483         // Assert the correct transaction association
  484         Transaction current = tm.getTransaction();
  485         if ((tx == null && current != null) || tx.equals(current) == false)
  486            throw new IllegalStateException("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 e)
  505         {
  506            throwJBossException(e, invocation.getType());
  507         }
  508         catch (HeuristicMixedException e)
  509         {
  510            throwJBossException(e, invocation.getType());
  511         }
  512         catch (HeuristicRollbackException e)
  513         {
  514            throwJBossException(e, invocation.getType());
  515         }
  516         catch (SystemException e)
  517         {
  518            throwJBossException(e, invocation.getType());
  519         }
  520         catch (IllegalStateException e)
  521         {
  522            throwJBossException(e, invocation.getType());
  523         }
  524         finally
  525         {
  526            // reassociate the oldTransaction with the Invocation (even null)
  527            invocation.setTransaction(oldTx);
  528            // Always drop thread association even if committing or
  529            // rolling back the newTransaction because not all TMs
  530            // will drop thread associations when commit() or rollback()
  531            // are called through tx itself (see JTA spec that seems to
  532            // indicate that thread assoc is required to be dropped only
  533            // when commit() and rollback() are called through TransactionManager
  534            // interface)
  535            //tx has committed, so we can't throw txRolledbackException.
  536            tm.suspend();
  537            // Reset the transaction timeout (unless we didn't set it)
  538            if (oldTimeout != -1)
  539               tm.setTransactionTimeout(oldTimeout);
  540         }
  541      }
  542   
  543   
  544      // Protected  ----------------------------------------------------
  545   
  546      /**
  547       * Rethrow the exception as a rollback or rollback local
  548       *
  549       * @param e the exception
  550       * @param type the invocation type
  551       */
  552      protected void throwJBossException(Exception e, InvocationType type)
  553         throws TransactionRolledbackException
  554      {
  555         // Unwrap a nested exception if possible.  There is no
  556         // point in the extra wrapping, and the EJB spec should have
  557         // just used javax.transaction exceptions
  558         if (e instanceof NestedException)
  559            {
  560               NestedException rollback = (NestedException) e;
  561               if(rollback.getCause() instanceof Exception)
  562               {
  563                  e = (Exception) rollback.getCause();
  564               }
  565            }
  566            if (type == InvocationType.LOCAL
  567                || type == InvocationType.LOCALHOME)
  568            {
  569               throw new JBossTransactionRolledbackLocalException(e);
  570            }
  571            else
  572            {
  573               throw new JBossTransactionRolledbackException(e);
  574            }
  575      }
  576   
  577      /**
  578       * The application has not thrown an exception, but...
  579       * When exception-on-rollback is true,
  580       * check whether the transaction is not active.
  581       * If it did not throw an exception anyway.
  582       * 
  583       * @param tx the transaction
  584       * @param type the invocation type
  585       * @throws TransactionRolledbackException if transaction is no longer active
  586       */
  587      protected void checkTransactionStatus(Transaction tx, InvocationType type)
  588         throws TransactionRolledbackException
  589      {
  590         if (exceptionRollback)
  591         {
  592            if (log.isTraceEnabled())
  593               log.trace("No exception from ejb, checking transaction status: " + tx);
  594            int status = Status.STATUS_UNKNOWN;
  595            try
  596            {
  597               status = tx.getStatus();
  598            }
  599            catch (Throwable t)
  600            {
  601               log.debug("Ignored error trying to retrieve transaction status", t);
  602            }
  603            if (status != Status.STATUS_ACTIVE)
  604            {
  605               Exception e = new Exception("Transaction cannot be committed (probably transaction timeout): " + tx);
  606               throwJBossException(e, type);
  607            }
  608         }
  609      }
  610      
  611      // Inner classes -------------------------------------------------
  612   
  613      // Monitorable implementation ------------------------------------
  614      public void sample(Object s)
  615      {
  616         // Just here to because Monitorable request it but will be removed soon
  617      }
  618      public Map retrieveStatistic()
  619      {
  620         return null;
  621      }
  622      public void resetStatistic()
  623      {
  624      }
  625   }

Save This Page
Home » jboss-5.0.0.CR1-src » org » jboss » ejb » plugins » [javadoc | source]