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.util.TimerTask;
   26   
   27   import javax.ejb.EJBException;
   28   import javax.transaction.RollbackException;
   29   import javax.transaction.Status;
   30   import javax.transaction.Synchronization;
   31   import javax.transaction.Transaction;
   32   
   33   import org.jboss.ejb.BeanLock;
   34   import org.jboss.ejb.Container;
   35   import org.jboss.ejb.EntityCache;
   36   import org.jboss.ejb.EntityContainer;
   37   import org.jboss.ejb.EntityEnterpriseContext;
   38   import org.jboss.ejb.GlobalTxEntityMap;
   39   import org.jboss.invocation.Invocation;
   40   import org.jboss.metadata.ConfigurationMetaData;
   41   import org.jboss.util.NestedRuntimeException;
   42   
   43   /**
   44    * The role of this interceptor is to synchronize the state of the cache with
   45    * the underlying storage.  It does this with the ejbLoad and ejbStore
   46    * semantics of the EJB specification.  In the presence of a transaction this
   47    * is triggered by transaction demarcation. It registers a callback with the
   48    * underlying transaction monitor through the JTA interfaces.  If there is no
   49    * transaction the policy is to store state upon returning from invocation.
   50    * The synchronization polices A,B,C of the specification are taken care of
   51    * here.
   52    *
   53    * <p><b>WARNING: critical code</b>, get approval from senior developers
   54    *    before changing.
   55    *
   56    * @author <a href="mailto:marc.fleury@jboss.org">Marc Fleury</a>
   57    * @author <a href="mailto:Scott.Stark@jboss.org">Scott Stark</a>
   58    * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
   59    * @version $Revision: 59204 $
   60    */
   61   public class EntitySynchronizationInterceptor extends AbstractInterceptor
   62   {
   63      /** Task for refreshing contexts */
   64      private ValidContextsRefresher vcr;
   65   
   66      /**
   67       *  The current commit option.
   68       */
   69      protected int commitOption;
   70   
   71      /**
   72       *  The refresh rate for commit option d
   73       */
   74      protected long optionDRefreshRate;
   75   
   76      /**
   77       *  The container of this interceptor.
   78       */
   79      protected EntityContainer container;
   80   
   81      public Container getContainer()
   82      {
   83         return container;
   84      }
   85   
   86      public void setContainer(Container container)
   87      {
   88         this.container = (EntityContainer) container;
   89      }
   90   
   91      public void create()
   92              throws Exception
   93      {
   94   
   95         try
   96         {
   97            ConfigurationMetaData configuration = container.getBeanMetaData().getContainerConfiguration();
   98            commitOption = configuration.getCommitOption();
   99            optionDRefreshRate = configuration.getOptionDRefreshRate();
  100         }
  101         catch(Exception e)
  102         {
  103            log.warn(e.getMessage());
  104         }
  105      }
  106   
  107      public void start()
  108      {
  109         try
  110         {
  111            //start up the validContexts thread if commit option D
  112            if (commitOption == ConfigurationMetaData.D_COMMIT_OPTION)
  113            {
  114               vcr = new ValidContextsRefresher();
  115               LRUEnterpriseContextCachePolicy.tasksTimer.schedule(vcr, optionDRefreshRate, optionDRefreshRate);
  116               log.debug("Scheduled a cache flush every " + optionDRefreshRate/1000 + " seconds");
  117            }
  118         }
  119         catch(Exception e)
  120         {
  121            vcr = null;
  122            log.warn("problem scheduling valid contexts refresher", e);
  123         }
  124      }
  125   
  126      public void stop()
  127      {
  128         if (vcr != null)
  129         {
  130            TimerTask temp = vcr;
  131            vcr = null;
  132            temp.cancel();
  133         }
  134      }
  135   
  136      protected Synchronization createSynchronization(Transaction tx, EntityEnterpriseContext ctx)
  137      {
  138         return new InstanceSynchronization(tx, ctx);
  139      }
  140   
  141      /**
  142       *  Register a transaction synchronization callback with a context.
  143       */
  144      protected void register(EntityEnterpriseContext ctx, Transaction tx)
  145      {
  146         boolean trace = log.isTraceEnabled();
  147         if(trace)
  148            log.trace("register, ctx=" + ctx + ", tx=" + tx);
  149   
  150         EntityContainer ctxContainer = null;
  151         try
  152         {
  153            ctxContainer = (EntityContainer)ctx.getContainer();
  154            if(!ctx.hasTxSynchronization())
  155            {
  156               // Create a new synchronization
  157               Synchronization synch = createSynchronization(tx, ctx);
  158   
  159               // We want to be notified when the transaction commits
  160               tx.registerSynchronization(synch);
  161   
  162               ctx.hasTxSynchronization(true);
  163            }
  164            //mark it dirty in global tx entity map if it is not read only
  165            if(!ctxContainer.isReadOnly())
  166            {
  167               ctx.getTxAssociation().scheduleSync(tx, ctx);
  168            }
  169         }
  170         catch(RollbackException e)
  171         {
  172            // The state in the instance is to be discarded, we force a reload of state
  173            synchronized(ctx)
  174            {
  175               ctx.setValid(false);
  176               ctx.hasTxSynchronization(false);
  177               ctx.setTransaction(null);
  178               ctx.setTxAssociation(GlobalTxEntityMap.NONE);
  179            }
  180            throw new EJBException(e);
  181         }
  182         catch(Throwable t)
  183         {
  184            // If anything goes wrong with the association remove the ctx-tx association
  185            ctx.hasTxSynchronization(false);
  186            ctx.setTxAssociation(GlobalTxEntityMap.NONE);
  187            if(t instanceof RuntimeException)
  188               throw (RuntimeException)t;
  189            else if(t instanceof Error)
  190               throw (Error)t;
  191            else if(t instanceof Exception)
  192               throw new EJBException((Exception)t);
  193            else
  194               throw new NestedRuntimeException(t);
  195         }
  196      }
  197   
  198      public Object invokeHome(Invocation mi) throws Exception
  199      {
  200         EntityEnterpriseContext ctx = (EntityEnterpriseContext)mi.getEnterpriseContext();
  201         Transaction tx = mi.getTransaction();
  202   
  203         Object rtn = getNext().invokeHome(mi);
  204   
  205         // An anonymous context was sent in, so if it has an id it is a real instance now
  206         if(ctx.getId() != null)
  207         {
  208   
  209            // it doesn't need to be read, but it might have been changed from the db already.
  210            ctx.setValid(true);
  211   
  212            if(tx != null)
  213            {
  214               BeanLock lock = container.getLockManager().getLock(ctx.getCacheKey());
  215               try
  216               {
  217                  lock.schedule(mi);
  218                  register(ctx, tx); // Set tx
  219                  lock.endInvocation(mi);
  220               }
  221               finally
  222               {
  223                  container.getLockManager().removeLockRef(lock.getId());
  224               }
  225            }
  226         }
  227         return rtn;
  228      }
  229   
  230      public Object invoke(Invocation mi) throws Exception
  231      {
  232         // We are going to work with the context a lot
  233         EntityEnterpriseContext ctx = (EntityEnterpriseContext)mi.getEnterpriseContext();
  234   
  235         // The Tx coming as part of the Method Invocation
  236         Transaction tx = mi.getTransaction();
  237   
  238         if(log.isTraceEnabled())
  239            log.trace("invoke called for ctx " + ctx + ", tx=" + tx);
  240   
  241         if(!ctx.isValid())
  242         {
  243            container.getPersistenceManager().loadEntity(ctx);
  244            ctx.setValid(true);
  245         }
  246   
  247         // mark the context as read only if this is a readonly method and the context
  248         // was not already readonly
  249         boolean didSetReadOnly = false;
  250         if(!ctx.isReadOnly() &&
  251            (container.isReadOnly() ||
  252            container.getBeanMetaData().isMethodReadOnly(mi.getMethod())))
  253         {
  254            ctx.setReadOnly(true);
  255            didSetReadOnly = true;
  256         }
  257   
  258         // So we can go on with the invocation
  259   
  260         // Invocation with a running Transaction
  261         try
  262         {
  263            if(tx != null && tx.getStatus() != Status.STATUS_NO_TRANSACTION)
  264            {
  265               // readonly does not synchronize, lock or belong with transaction.
  266               boolean isReadOnly = container.isReadOnly();
  267               if(isReadOnly == false)
  268               {
  269                  Method method = mi.getMethod();
  270                  if(method != null)
  271                     isReadOnly = container.getBeanMetaData().isMethodReadOnly(method.getName());
  272               }
  273               try
  274               {
  275                  if(isReadOnly == false)
  276                  {
  277                     // register the wrapper with the transaction monitor (but only
  278                     // register once). The transaction demarcation will trigger the
  279                     // storage operations
  280                     register(ctx, tx);
  281                  }
  282   
  283                  //Invoke down the chain
  284                  Object retVal = getNext().invoke(mi);
  285   
  286                  // Register again as a finder in the middle of a method
  287                  // will de-register this entity, and then the rest of the method can
  288                  // change fields which will never be stored
  289                  if(isReadOnly == false)
  290                  {
  291                     // register the wrapper with the transaction monitor (but only
  292                     // register once). The transaction demarcation will trigger the
  293                     // storage operations
  294                     register(ctx, tx);
  295                  }
  296   
  297                  // return the return value
  298                  return retVal;
  299               }
  300               finally
  301               {
  302                  // We were read-only and the context wasn't already synchronized, tidyup the cache
  303                  if(isReadOnly && ctx.hasTxSynchronization() == false)
  304                  {
  305                     switch(commitOption)
  306                     {
  307                        // Keep instance active, but invalidate state
  308                        case ConfigurationMetaData.B_COMMIT_OPTION:
  309                           // Invalidate state (there might be other points of entry)
  310                           ctx.setValid(false);
  311                           break;
  312   
  313                           // Invalidate everything AND Passivate instance
  314                        case ConfigurationMetaData.C_COMMIT_OPTION:
  315                           try
  316                           {
  317                              // FIXME: We cannot passivate here, because previous
  318                              // interceptors work with the context, in particular
  319                              // the re-entrance interceptor is doing lock counting
  320                              // Just remove it from the cache
  321                              if(ctx.getId() != null)
  322                                 container.getInstanceCache().remove(ctx.getId());
  323                           }
  324                           catch(Exception e)
  325                           {
  326                              log.debug("Exception releasing context", e);
  327                           }
  328                           break;
  329                     }
  330                  }
  331               }
  332            }
  333            else
  334            {
  335               // No tx
  336               try
  337               {
  338                  Object result = getNext().invoke(mi);
  339   
  340                  // Store after each invocation -- not on exception though, or removal
  341                  // And skip reads too ("get" methods)
  342                  if(ctx.getId() != null && !container.isReadOnly())
  343                  {
  344                     container.invokeEjbStore(ctx);
  345                     container.storeEntity(ctx);
  346                  }
  347   
  348                  return result;
  349               }
  350               catch(Exception e)
  351               {
  352                  // Exception - force reload on next call
  353                  ctx.setValid(false);
  354                  throw e;
  355               }
  356               finally
  357               {
  358                  switch(commitOption)
  359                  {
  360                     // Keep instance active, but invalidate state
  361                     case ConfigurationMetaData.B_COMMIT_OPTION:
  362                        // Invalidate state (there might be other points of entry)
  363                        ctx.setValid(false);
  364                        break;
  365   
  366                        // Invalidate everything AND Passivate instance
  367                     case ConfigurationMetaData.C_COMMIT_OPTION:
  368                        try
  369                        {
  370                           // Do not call release if getId() is null.  This means that
  371                           // the entity has been removed from cache.
  372                           // release will schedule a passivation and this removed ctx
  373                           // could be put back into the cache!
  374                           // This is necessary because we have no lock, we
  375                           // don't want to return an instance to the pool that is
  376                           // being used
  377                           if(ctx.getId() != null)
  378                              container.getInstanceCache().remove(ctx.getId());
  379                        }
  380                        catch(Exception e)
  381                        {
  382                           log.debug("Exception releasing context", e);
  383                        }
  384                        break;
  385                  }
  386               }
  387            }
  388         }
  389         finally
  390         {
  391            // if we marked the context as read only we need to reset it
  392            if(didSetReadOnly)
  393            {
  394               ctx.setReadOnly(false);
  395            }
  396         }
  397      }
  398   
  399      protected class InstanceSynchronization
  400              implements Synchronization
  401      {
  402         /**
  403          *  The transaction we follow.
  404          */
  405         protected Transaction tx;
  406   
  407         /**
  408          *  The context we manage.
  409          */
  410         protected EntityEnterpriseContext ctx;
  411   
  412         /**
  413          * The context lock
  414          */
  415         protected BeanLock lock;
  416   
  417         /**
  418          *  Create a new instance synchronization instance.
  419          */
  420         InstanceSynchronization(Transaction tx, EntityEnterpriseContext ctx)
  421         {
  422            this.tx = tx;
  423            this.ctx = ctx;
  424            this.lock = container.getLockManager().getLock(ctx.getCacheKey());
  425         }
  426   
  427         public void beforeCompletion()
  428         {
  429            //synchronization is handled by GlobalTxEntityMap.
  430         }
  431   
  432         public void afterCompletion(int status)
  433         {
  434            boolean trace = log.isTraceEnabled();
  435   
  436            // This is an independent point of entry. We need to make sure the
  437            // thread is associated with the right context class loader
  438            ClassLoader oldCl = SecurityActions.getContextClassLoader();
  439            boolean setCl = !oldCl.equals(container.getClassLoader());
  440            if(setCl)
  441            {
  442               SecurityActions.setContextClassLoader(container.getClassLoader());
  443            }
  444            container.pushENC();
  445   
  446            int commitOption = ctx.isPassivateAfterCommit() ?
  447               ConfigurationMetaData.C_COMMIT_OPTION : EntitySynchronizationInterceptor.this.commitOption;
  448   
  449            lock.sync();
  450            // The context is no longer synchronized on the TX
  451            ctx.hasTxSynchronization(false);
  452            ctx.setTxAssociation(GlobalTxEntityMap.NONE);
  453            ctx.setTransaction(null);
  454            try
  455            {
  456               try
  457               {
  458                  // If rolled back -> invalidate instance
  459                  if(status == Status.STATUS_ROLLEDBACK)
  460                  {
  461                     // remove from the cache
  462                     container.getInstanceCache().remove(ctx.getCacheKey());
  463                  }
  464                  else
  465                  {
  466                     switch(commitOption)
  467                     {
  468                        // Keep instance cached after tx commit
  469                        case ConfigurationMetaData.A_COMMIT_OPTION:
  470                        case ConfigurationMetaData.D_COMMIT_OPTION:
  471                           // The state is still valid (only point of access is us)
  472                           ctx.setValid(true);
  473                           break;
  474   
  475                           // Keep instance active, but invalidate state
  476                        case ConfigurationMetaData.B_COMMIT_OPTION:
  477                           // Invalidate state (there might be other points of entry)
  478                           ctx.setValid(false);
  479                           break;
  480                           // Invalidate everything AND Passivate instance
  481                        case ConfigurationMetaData.C_COMMIT_OPTION:
  482                           try
  483                           {
  484                              // We weren't removed, passivate
  485                              // Here we own the lock, so we don't try to passivate
  486                              // we just passivate
  487                              if(ctx.getId() != null)
  488                              {
  489                                 container.getInstanceCache().remove(ctx.getId());
  490                                 container.getPersistenceManager().passivateEntity(ctx);
  491                              }
  492                              // If we get this far, we return to the pool
  493                              container.getInstancePool().free(ctx);
  494                           }
  495                           catch(Exception e)
  496                           {
  497                              log.debug("Exception releasing context", e);
  498                           }
  499                           break;
  500                     }
  501                  }
  502               }
  503               finally
  504               {
  505                  if(trace)
  506                     log.trace("afterCompletion, clear tx for ctx=" + ctx + ", tx=" + tx);
  507                  lock.endTransaction(tx);
  508   
  509                  if(trace)
  510                     log.trace("afterCompletion, sent notify on TxLock for ctx=" + ctx);
  511               }
  512            } // synchronized(lock)
  513            finally
  514            {
  515               lock.releaseSync();
  516               container.getLockManager().removeLockRef(lock.getId());
  517               container.popENC();
  518               if(setCl)
  519               {
  520                  SecurityActions.setContextClassLoader(oldCl);
  521               }
  522            }
  523         }
  524   
  525      }
  526   
  527      /**
  528       * Flushes the cache according to the optiond refresh rate.
  529       */
  530      class ValidContextsRefresher extends TimerTask
  531      {
  532         public ValidContextsRefresher()
  533         {
  534         }
  535         
  536         public void run()
  537         {
  538            // Guard against NPE at shutdown
  539            if (container == null)
  540            {
  541               cancel();
  542               return;
  543            }
  544            
  545            if(log.isTraceEnabled())
  546               log.trace("Flushing the valid contexts " + container.getBeanMetaData().getEjbName());
  547   
  548            EntityCache cache = (EntityCache) container.getInstanceCache();
  549            try
  550            {
  551               if(cache != null)
  552                  cache.flush();
  553            }
  554            catch (Throwable t)
  555            {
  556               log.debug("Ignored error while trying to flush() entity cache", t);
  557            }
  558         }
  559      }
  560   }

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