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 java.lang.reflect.Method;
   25   import java.rmi.NoSuchObjectException;
   26   import java.rmi.RemoteException;
   27   
   28   import javax.ejb.EJBException;
   29   import javax.ejb.EJBObject;
   30   import javax.ejb.Handle;
   31   import javax.ejb.NoSuchObjectLocalException;
   32   import javax.ejb.TimedObject;
   33   import javax.ejb.Timer;
   34   import javax.transaction.RollbackException;
   35   import javax.transaction.Status;
   36   import javax.transaction.Synchronization;
   37   import javax.transaction.Transaction;
   38   
   39   import org.jboss.ejb.AllowedOperationsAssociation;
   40   import org.jboss.ejb.BeanLock;
   41   import org.jboss.ejb.Container;
   42   import org.jboss.ejb.EnterpriseContext;
   43   import org.jboss.ejb.InstanceCache;
   44   import org.jboss.ejb.InstancePool;
   45   import org.jboss.ejb.StatefulSessionContainer;
   46   import org.jboss.invocation.Invocation;
   47   import org.jboss.invocation.InvocationType;
   48   import org.jboss.logging.Logger;
   49   import org.jboss.metadata.SessionMetaData;
   50   import org.jboss.security.AuthenticationManager;
   51   import org.jboss.security.SecurityConstants;
   52   
   53   /**
   54    * This container acquires the given instance.
   55    *
   56    * @author <a href="mailto:rickard.oberg@telkel.com">Rickard Oberg</a>
   57    * @author <a href="mailto:marc.fleury@jboss.org">Marc Fleury</a>
   58    * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
   59    * @author <a href="mailto:scott.stark@jboss.org">Scott Stark</a>
   60    * @version $Revision: 69133 $
   61    *
   62    */
   63   public class StatefulSessionInstanceInterceptor
   64      extends AbstractInterceptor
   65   {
   66      // Constants ----------------------------------------------------
   67      
   68      // Attributes ---------------------------------------------------
   69      
   70      /** Instance logger. */
   71      protected Logger log = Logger.getLogger(this.getClass());
   72      
   73      protected StatefulSessionContainer container;
   74      
   75      // Static -------------------------------------------------------
   76      
   77      private static final Method getEJBHome;
   78      private static final Method getHandle;
   79      private static final Method getPrimaryKey;
   80      private static final Method isIdentical;
   81      private static final Method remove;
   82      private static final Method getEJBObject;
   83      private static final Method ejbTimeout;
   84   
   85      static
   86      {
   87         try
   88         {
   89            Class[] noArg = new Class[0];
   90            getEJBHome = EJBObject.class.getMethod("getEJBHome", noArg);
   91            getHandle = EJBObject.class.getMethod("getHandle", noArg);
   92            getPrimaryKey = EJBObject.class.getMethod("getPrimaryKey", noArg);
   93            isIdentical = EJBObject.class.getMethod("isIdentical", new Class[]{EJBObject.class});
   94            remove = EJBObject.class.getMethod("remove", noArg);
   95            getEJBObject = Handle.class.getMethod("getEJBObject", noArg);
   96            ejbTimeout = TimedObject.class.getMethod("ejbTimeout", new Class[]{Timer.class});
   97         }
   98         catch (Exception e)
   99         {
  100            e.printStackTrace();
  101            throw new ExceptionInInitializerError(e);
  102         }
  103      }
  104      
  105      // Constructors -------------------------------------------------
  106      
  107      // Public -------------------------------------------------------
  108      
  109      public void setContainer(Container container)
  110      {
  111         this.container = (StatefulSessionContainer)container;
  112      }
  113      
  114      public  Container getContainer()
  115      {
  116         return container;
  117      }
  118      
  119      // Interceptor implementation -----------------------------------
  120      
  121      public Object invokeHome(Invocation mi)
  122         throws Exception
  123      {
  124         // Invocation on the handle, we don't need a bean instance
  125         if (getEJBObject.equals(mi.getMethod()))
  126            return getNext().invokeHome(mi);
  127   
  128         // get a new context from the pool (this is a home method call)
  129         InstancePool pool = container.getInstancePool();
  130         EnterpriseContext ctx = pool.get();
  131   
  132         // set the context on the Invocation
  133         mi.setEnterpriseContext(ctx);
  134         
  135         // It is a new context for sure so we can lock it
  136         ctx.lock();
  137         
  138         // Set the current security information
  139         /**
  140          * JBAS-3976: Setting principal on the context has been moved to a separate interceptor
  141          */ 
  142    
  143         AllowedOperationsAssociation.pushInMethodFlag(IN_EJB_HOME);
  144   
  145         try
  146         {
  147            // Invoke through interceptors
  148            return getNext().invokeHome(mi);
  149         }
  150         finally
  151         {
  152            synchronized (ctx)
  153            {
  154               AllowedOperationsAssociation.popInMethodFlag();
  155   
  156               // Release the lock
  157               ctx.unlock();
  158               
  159               // Still free? Not free if create() was called successfully
  160               if (ctx.getId() == null)
  161               {
  162                  pool.free(ctx);
  163               }
  164            }
  165         }
  166      }
  167      
  168      protected void register(EnterpriseContext ctx, Transaction tx, BeanLock lock)
  169      {
  170         // Create a new synchronization
  171         InstanceSynchronization synch = new InstanceSynchronization(ctx, lock);
  172         
  173         try
  174         {
  175            // OSH: An extra check to avoid warning.
  176            // Can go when we are sure that we no longer get
  177            // the JTA violation warning.
  178            if (tx.getStatus() == Status.STATUS_MARKED_ROLLBACK)
  179            {
  180               
  181               return;
  182            }
  183            
  184            // We want to be notified when the transaction commits
  185            try
  186            {
  187               tx.registerSynchronization(synch);
  188            }
  189            catch (Exception ex)
  190            {
  191               // synch adds a reference to the lock, so we must release the ref
  192               // because afterCompletion will never get called.
  193               getContainer().getLockManager().removeLockRef(lock.getId());
  194               throw ex;
  195            }
  196            
  197            // EJB 1.1, 6.5.3
  198            synch.afterBegin();
  199            
  200         } catch (RollbackException e)
  201         {
  202            
  203         } catch (Exception e)
  204         {
  205            
  206            throw new EJBException(e);
  207            
  208         }
  209      }
  210      
  211      public Object invoke(Invocation mi)
  212      throws Exception
  213      {
  214         InstanceCache cache = container.getInstanceCache();
  215         InstancePool pool = container.getInstancePool();
  216         Object methodID = mi.getId();
  217         EnterpriseContext ctx = null;
  218         
  219         BeanLock lock = container.getLockManager().getLock(methodID);
  220         boolean callerRunAsIdentityPresent = SecurityActions.peekRunAsIdentity() != null;
  221         boolean pushSecurityContext = SecurityActions.getSecurityContext() == null;
  222         try
  223         {
  224            /* The security context must be established before the cache
  225            lookup because the activation of a session should have the caller's
  226            security context as ejbActivate is allowed to call other secured
  227            resources. Since the pm makes the ejbActivate call, we need to
  228            set the caller's security context. The only reason this shows up for
  229            stateful session is that we moved the SecurityInterceptor to after
  230            the instance interceptor to allow security exceptions to result in
  231            invalidation of the session. This may be too literal an interpretation
  232            of the ejb spec requirement that runtime exceptions should invalidate
  233            the session.
  234             */
  235            if(!callerRunAsIdentityPresent && pushSecurityContext)
  236            {
  237               AuthenticationManager am = container.getSecurityManager();
  238               String securityDomain = SecurityConstants.DEFAULT_APPLICATION_POLICY;
  239               if(am != null)
  240                  securityDomain = am.getSecurityDomain();
  241               SecurityActions.createAndSetSecurityContext(mi.getPrincipal(), mi.getCredential(), 
  242                     securityDomain , null);
  243               //SecurityActions.pushSubjectContext(mi.getPrincipal(), mi.getCredential(), null);
  244            }
  245   
  246            lock.sync();
  247            try
  248            {           
  249               // Get context
  250               try
  251               {
  252                  ctx = cache.get(methodID);
  253               }
  254               catch (NoSuchObjectException e)
  255               {
  256                  if (mi.isLocal())
  257                     throw new NoSuchObjectLocalException(e.getMessage());
  258                  else
  259                     throw e;
  260               }
  261               catch (EJBException e)
  262               {
  263                  throw e;
  264               }
  265               catch (RemoteException e)
  266               {
  267                  throw e;
  268               }
  269               catch (Exception e)
  270               {
  271                  InvocationType type = mi.getType();
  272                  boolean isLocal = (type == InvocationType.LOCAL || type == InvocationType.LOCALHOME);
  273                  if (isLocal)
  274                     throw new EJBException("Unable to get an instance from the pool/cache", e);
  275                  else
  276                     throw new RemoteException("Unable to get an intance from the pool/cache", e);
  277               }
  278               
  279               // Associate it with the method invocation
  280               mi.setEnterpriseContext(ctx);
  281               // Set the JACC EnterpriseBean PolicyContextHandler data
  282               EnterpriseBeanPolicyContextHandler.setEnterpriseBean(ctx.getInstance());
  283   
  284               // BMT beans will lock and replace tx no matter what, CMT do work on transaction
  285               boolean isBMT = ((SessionMetaData)container.getBeanMetaData()).isBeanManagedTx();
  286               if (isBMT == false)
  287               {
  288                  
  289                  // Do we have a running transaction with the context
  290                  if (ctx.getTransaction() != null &&
  291                  // And are we trying to enter with another transaction
  292                  !ctx.getTransaction().equals(mi.getTransaction()))
  293                  {
  294                     // Calls must be in the same transaction
  295                     StringBuffer msg = new StringBuffer("Application Error: " +
  296                        "tried to enter Stateful bean with different tx context");
  297                     msg.append(", contextTx: " + ctx.getTransaction());
  298                     msg.append(", methodTx: " + mi.getTransaction());
  299                     throw new EJBException(msg.toString());
  300                  }
  301   
  302                  //If the instance will participate in a new transaction we register a sync for it
  303                  if (ctx.getTransaction() == null && mi.getTransaction() != null)
  304                  {
  305                     register(ctx, mi.getTransaction(), lock);
  306                  }
  307               }
  308               
  309               if (!ctx.isLocked())
  310               {
  311                  
  312                  //take it!
  313                  ctx.lock();
  314               }
  315               else
  316               {
  317                  if (!isCallAllowed(mi))
  318                  {
  319                     // Concurent calls are not allowed
  320                     throw new EJBException("Application Error: no concurrent " +
  321                           "calls on stateful beans");
  322                  }
  323                  else
  324                  {
  325                     ctx.lock();
  326                  }
  327               }
  328            }
  329            finally
  330            {
  331               lock.releaseSync();
  332            }
  333   
  334            // Set the current security information
  335            /**
  336             * JBAS-3976: Setting principal on the context has been moved to a separate interceptor
  337             */ 
  338   
  339            if (ejbTimeout.equals(mi.getMethod()))
  340               AllowedOperationsAssociation.pushInMethodFlag(IN_EJB_TIMEOUT);
  341            else
  342               AllowedOperationsAssociation.pushInMethodFlag(IN_BUSINESS_METHOD);
  343   
  344            boolean validContext = true;
  345            try
  346            {
  347               // Invoke through interceptors
  348               Object ret = getNext().invoke(mi);
  349               return ret;
  350            }
  351            catch (RemoteException e)
  352            {
  353               // Discard instance
  354               cache.remove(methodID);
  355               pool.discard(ctx);
  356               validContext = false;
  357   
  358               throw e;
  359            }
  360            catch (RuntimeException e)
  361            {
  362               // Discard instance
  363               cache.remove(methodID);
  364               pool.discard(ctx);
  365               validContext = false;
  366   
  367               throw e;
  368            }
  369            catch (Error e)
  370            {
  371               // Discard instance
  372               cache.remove(methodID);
  373               pool.discard(ctx);
  374               validContext = false;
  375   
  376               throw e;
  377            }
  378            finally
  379            {
  380               AllowedOperationsAssociation.popInMethodFlag();
  381   
  382               if (validContext)
  383               {
  384                  // Still a valid instance
  385                  lock.sync();
  386                  try
  387                  {
  388   
  389                     // release it
  390                     ctx.unlock();
  391                     
  392                     // if removed, remove from cache
  393                     if (ctx.getId() == null)
  394                     {
  395                        // Remove from cache
  396                        cache.remove(methodID);
  397                        pool.free(ctx);
  398                     }
  399                  }
  400                  finally
  401                  {
  402                     lock.releaseSync();
  403                  }
  404               }
  405            }
  406         }
  407         finally
  408         {
  409            container.getLockManager().removeLockRef(lock.getId());
  410            if(!callerRunAsIdentityPresent && pushSecurityContext)
  411              SecurityActions.clearSecurityContext();
  412            EnterpriseBeanPolicyContextHandler.setEnterpriseBean(null);
  413         }
  414      }
  415      
  416      protected boolean isCallAllowed(Invocation mi)
  417      {
  418         Method m = mi.getMethod();
  419         if (m.equals(getEJBHome) ||
  420         m.equals(getHandle) ||
  421         m.equals(getPrimaryKey) ||
  422         m.equals(isIdentical) ||
  423         m.equals(remove))
  424         {
  425            return true;
  426         }
  427         return false;
  428      }
  429      
  430      // Inner classes -------------------------------------------------
  431      
  432      private class InstanceSynchronization
  433      implements Synchronization
  434      {
  435         /**
  436          *  The context we manage.
  437          */
  438         private EnterpriseContext ctx;
  439         
  440         // a utility boolean for session sync
  441         private boolean notifySession = false;
  442         
  443         // Utility methods for the notifications
  444         private Method afterBegin;
  445         private Method beforeCompletion;
  446         private Method afterCompletion;
  447         private BeanLock lock;
  448         private boolean beforeCompletionInvoked = false;
  449         
  450         /**
  451          *  Create a new instance synchronization instance.
  452          */
  453         InstanceSynchronization(EnterpriseContext ctx, BeanLock lock)
  454         {
  455            this.ctx = ctx;
  456            this.lock = lock;
  457            this.lock.addRef();
  458            
  459            // Let's compute it now, to speed things up we could
  460            notifySession = (ctx.getInstance() instanceof javax.ejb.SessionSynchronization);
  461            
  462            if (notifySession)
  463            {
  464               try
  465               {
  466                  // Get the class we are working on
  467                  Class sync = Class.forName("javax.ejb.SessionSynchronization");
  468                  
  469                  // Lookup the methods on it
  470                  afterBegin = sync.getMethod("afterBegin", new Class[0]);
  471                  beforeCompletion = sync.getMethod("beforeCompletion", new Class[0]);
  472                  afterCompletion =  sync.getMethod("afterCompletion", new Class[]
  473                  {boolean.class});
  474               }
  475               catch (Exception e)
  476               {
  477                  log.error("failed to setup InstanceSynchronization", e);
  478               }
  479            }
  480         }
  481         
  482         // Synchronization implementation -----------------------------
  483         
  484         public void afterBegin()
  485         {
  486            if (notifySession)
  487            {
  488               try
  489               {
  490                  AllowedOperationsAssociation.pushInMethodFlag(IN_AFTER_BEGIN);
  491                  afterBegin.invoke(ctx.getInstance(), new Object[0]);
  492               }
  493               catch (Exception e)
  494               {
  495                  log.error("failed to invoke afterBegin", e);
  496               }
  497               finally{
  498                  AllowedOperationsAssociation.popInMethodFlag();
  499               }
  500            }
  501         }
  502         
  503         public void beforeCompletion()
  504         {
  505            if( log.isTraceEnabled() )
  506               log.trace("beforeCompletion called");
  507   
  508            // lock the context the transaction is being commited (no need for sync)
  509            ctx.lock();
  510            beforeCompletionInvoked = true;
  511            
  512            if (notifySession)
  513            {
  514               try
  515               {
  516                  AllowedOperationsAssociation.pushInMethodFlag(IN_BEFORE_COMPLETION);
  517                  beforeCompletion.invoke(ctx.getInstance(), new Object[0]);
  518               }
  519               catch (Exception e)
  520               {
  521                  log.error("failed to invoke beforeCompletion", e);
  522               }
  523               finally{
  524                  AllowedOperationsAssociation.popInMethodFlag();
  525               }
  526            }
  527         }
  528         
  529         public void afterCompletion(int status)
  530         {
  531            if( log.isTraceEnabled() )
  532               log.trace("afterCompletion called");
  533            
  534            lock.sync();
  535            try
  536            {
  537               // finish the transaction association
  538               ctx.setTransaction(null);
  539               
  540               if (beforeCompletionInvoked)
  541                  ctx.unlock();
  542               
  543               if (notifySession)
  544               {
  545                  
  546                  try
  547                  {
  548                     AllowedOperationsAssociation.pushInMethodFlag(IN_AFTER_COMPLETION);
  549                     if (status == Status.STATUS_COMMITTED)
  550                     {
  551                        afterCompletion.invoke(ctx.getInstance(), new Object[]{Boolean.TRUE});
  552                     }
  553                     else
  554                     {
  555                        afterCompletion.invoke(ctx.getInstance(), new Object[]{Boolean.FALSE});
  556                     }
  557                  }
  558                  catch (Exception e)
  559                  {
  560                     log.error("failed to invoke afterCompletion", e);
  561                  }
  562                  finally{
  563                     AllowedOperationsAssociation.popInMethodFlag();
  564                  }
  565               }
  566            }
  567            finally
  568            {
  569               lock.releaseSync();
  570               container.getLockManager().removeLockRef(lock.getId());
  571            }
  572         }
  573      }
  574   }
  575   

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