/**
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */

package org.jboss.cache.invalidation.triggers;

import java.io.Serializable;
import javax.transaction.Transaction;
import javax.transaction.Status;

import org.jboss.ejb.plugins.AbstractInterceptor;
import org.jboss.ejb.EntityContainer;
import org.jboss.ejb.EntityEnterpriseContext;
import org.jboss.ejb.Container;
import org.jboss.cache.invalidation.InvalidationManagerMBean;
import org.jboss.cache.invalidation.InvalidationGroup;
import org.jboss.cache.invalidation.InvalidationsTxGrouper;
import org.jboss.metadata.EntityMetaData;
import org.jboss.metadata.MetaData;
import org.jboss.metadata.XmlLoadable;
import org.jboss.system.Registry;
import org.jboss.invocation.Invocation;
import org.w3c.dom.Element;

/**
 * The role of this interceptor is to detect that an entity has been modified
 * after an invocation has been performed an use the InvalidationsTxGrouper
 * static class so that invalidation messages can be groupped and
 * sent at transaction-commit time in a single batch.
 *
 * @author <a href="mailto:sacha.labourey@cogito-info.ch">Sacha Labourey</a>
 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
 * @version $Revision: 1.2.2.4 $
 */
public class EntityBeanCacheBatchInvalidatorInterceptor
   extends AbstractInterceptor
   implements XmlLoadable
{
   protected boolean doCacheInvalidations = true;
   protected InvalidationManagerMBean invalMgr = null;
   protected InvalidationGroup ig = null;
   protected EntityContainer container = null;
   protected boolean trace;
   private boolean invalidateRelated;

   public void start() throws Exception
   {
      EntityMetaData emd = (EntityMetaData) this.getContainer().getBeanMetaData();
      doCacheInvalidations = emd.doDistributedCacheInvalidations();
      trace = log.isTraceEnabled();
      if( doCacheInvalidations )
      {
         // we are interested in receiving cache invalidation callbacks
         //
         String groupName = emd.getDistributedCacheInvalidationConfig().getInvalidationGroupName();
         String imName = emd.getDistributedCacheInvalidationConfig().getInvalidationManagerName();

         this.invalMgr = (InvalidationManagerMBean) Registry.lookup(imName);
         this.ig = this.invalMgr.getInvalidationGroup(groupName);
      }
   }

   public void stop()
   {
      this.invalMgr = null;
      // ig can be null if cache-invalidation is false
      if( ig != null )
      {
         this.ig.removeReference(); // decrease the usage counter
         this.ig = null;
      }
   }

   // Interceptor implementation --------------------------------------

   protected boolean changed(Invocation mi, EntityEnterpriseContext ctx)
      throws Exception
   {
      if( ctx.getId() == null ) return true;

      if( !container.isReadOnly() )
      {
         java.lang.reflect.Method method = mi.getMethod();
         if( method == null ||
            !container.getBeanMetaData().isMethodReadOnly(method.getName()) )
         {
            return invalidateRelated ?
               container.getPersistenceManager().isModified(ctx) :
               container.getPersistenceManager().isStoreRequired(ctx);
         }
      }
      return false;
   }

   public Object invoke(Invocation mi) throws Exception
   {
      if( doCacheInvalidations )
      {
         // We are going to work with the context a lot
         EntityEnterpriseContext ctx = (EntityEnterpriseContext) mi.getEnterpriseContext();
         Serializable id = (Serializable) ctx.getId();

         // The Tx coming as part of the Method Invocation
         Transaction tx = mi.getTransaction();

         // Invocation with a running Transaction
         if( tx != null && tx.getStatus() != Status.STATUS_NO_TRANSACTION )
         {
            //Invoke down the chain
            Object retVal = getNext().invoke(mi);

            if( changed(mi, ctx) )
            {
               if( trace )
                  log.trace("invoke:tx, id="+id+" changed");
               InvalidationsTxGrouper.registerInvalidationSynchronization(tx, this.ig, id);
            }

            // return the return value
            return retVal;
         }
         //
         else
         { // No tx
            Object result = getNext().invoke(mi);

            if( changed(mi, ctx) )
            {
               if( trace )
                  log.trace("invoke:no-tx, id="+id+" changed");
               InvalidationsTxGrouper.registerInvalidationSynchronization(tx, this.ig, id);
            }
            return result;
         }
      }
      else
      {
         return getNext().invoke(mi);
      }
   }

   public void setContainer(Container container)
   {
      this.container = (EntityContainer) container;
   }

   public Container getContainer()
   {
      return container;
   }

   // XmlLoadable implementation --------------------------------------------------------

   public void importXml(Element element)
   {
      String str = MetaData.getElementAttribute(element, "invalidate-related");
      invalidateRelated = (str == null ? true : Boolean.valueOf(str).booleanValue());
      if(log.isTraceEnabled())
      {
         log.trace("invalidate-related: " + invalidateRelated);
      }
   }
}
