/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.cache.invalidation;

import java.io.Serializable;
import java.util.HashSet;

import javax.management.MBeanParameterInfo;
import javax.management.MBeanOperationInfo;

import org.jboss.cache.invalidation.InvalidationManager.BridgeInvalidationSubscriptionImpl;

/**
 * Implementation of InvalidationManagerMBean
 *
 * @see org.jboss.cache.invalidation.InvalidationManagerMBean
 *
 * @author  <a href="mailto:sacha.labourey@cogito-info.ch">Sacha Labourey</a>.
 * @version $Revision: 81030 $
 */
public class InvalidationManager 
   extends org.jboss.system.ServiceMBeanSupport 
   implements InvalidationManagerMBean 
{
   
   // Constants -----------------------------------------------------
   
   public static final String DEFAULT_JMX_SERVICE_NAME = "jboss.cache:service=InvalidationManager";
   public static final String DEFAULT_INVALIDERS_JMX_NAME = "jboss.cache:service=InvalidationGroup";
   
   // Attributes ----------------------------------------------------
   
   protected java.util.Hashtable groups = new java.util.Hashtable ();
   protected java.util.Vector bridgeSubscribers = new java.util.Vector ();
   protected int hashcode = 0;
   
   protected boolean DEFAULT_TO_ASYNCHRONOUS_MODE = false;
   
   // Static --------------------------------------------------------
   
   // Constructors --------------------------------------------------
   
   public InvalidationManager () { super(); }
   
   public void startService () throws Exception
   {
      // bind us in system registry
      //
      log.debug ("Starting Invalidation Manager " + this.getServiceName ().toString ());
      org.jboss.system.Registry.bind (this.getServiceName ().toString (), this);
      this.hashcode = this.getServiceName ().hashCode ();
   }
   
   public void stopService ()
   {
      log.debug ("Stoping Invalidation Manager " + this.getServiceName ().toString ());
      org.jboss.system.Registry.unbind (this.getServiceName ().toString ());
   }
   
   public boolean getIsAsynchByDefault()
   {
      return DEFAULT_TO_ASYNCHRONOUS_MODE;
   }
   public void setIsAsynchByDefault(boolean flag)
   {
      this.DEFAULT_TO_ASYNCHRONOUS_MODE = flag;
   }
   
   public java.util.Collection getInvalidationGroups ()
   {
      return this.groups.values ();
   }
   
   public InvalidationGroup getInvalidationGroup (String groupName)
   {
      synchronized (this.groups)
      {
         InvalidationGroup group = (InvalidationGroup)this.groups.get (groupName);
         if (group == null)
         {
            group = createGroup (groupName);
         }

         group.addReference ();

         return group;
      }
   }   

   public synchronized BridgeInvalidationSubscription registerBridgeListener (InvalidationBridgeListener listener)
   {
      log.debug ("Subscribing a new cache-invalidation bridge");
      BridgeInvalidationSubscription subs = new BridgeInvalidationSubscriptionImpl(listener);

      java.util.Vector newVector = new java.util.Vector (this.bridgeSubscribers);
      newVector.add (subs);
      this.bridgeSubscribers = newVector;      
      
      return subs;
   }
   
   public void batchInvalidate (BatchInvalidation[] invalidations)
   {
      this.batchInvalidate (invalidations, this.DEFAULT_TO_ASYNCHRONOUS_MODE);
   }
   
   public void batchInvalidate (BatchInvalidation[] invalidations, boolean asynchronous)
   {
      if (log.isTraceEnabled ())
         log.trace ("Batch cache invalidation. Caches concerned: " + invalidations.length);
      
      this.crossDomainBatchInvalidate (null, invalidations, asynchronous);      
   }

   public void invalidateAll(String groupName)
   {
      invalidateAll(groupName, DEFAULT_TO_ASYNCHRONOUS_MODE);
   }

   public void invalidateAll(String groupName, boolean async)
   {
      if (log.isTraceEnabled ())
         log.trace ("Invalidate all for group: " + groupName);

      crossDomainInvalidateAll(null, groupName, async);
   }

   // Public --------------------------------------------------------
   
   // Z implementation ----------------------------------------------
   
   // Object overrides ---------------------------------------------------
   
   public int hashCode ()
   {
      return hashcode;
   }   
   // Package protected ---------------------------------------------
   
   // Protected -----------------------------------------------------

   protected InvalidationGroup createGroup (String groupName)
   {
      InvalidationGroup group = new org.jboss.cache.invalidation.InvalidationManager.InvalidationGroupImpl (groupName);
      this.groups.put (groupName, group);

      // register the group with JMX so it can be easyly remotly
      // reached (and thus cache can be invalidated)
      //
      try
      {
         log.debug ("Creating and registering a new InvalidationGroup: " + groupName);
         javax.management.ObjectName groupObjectName = new javax.management.ObjectName (DEFAULT_INVALIDERS_JMX_NAME + ",GroupName="+groupName);
         this.getServer ().registerMBean (group, groupObjectName);      
      }
      catch (Exception e)
      {
         log.debug ("Problem while trying to register a new invalidation group in JMX", e);
      }

      // warn bridges
      //
      log.debug ("Informing bridges about new group creation ...");
      for (int i=0; i<bridgeSubscribers.size (); i++)
         ((BridgeInvalidationSubscriptionImpl)(bridgeSubscribers.elementAt (i))).groupCreated (groupName);
      
      return group;
   }
   
   protected void removeGroup (String groupName)
   {
      synchronized (this.groups)
      {
         this.groups.remove (groupName);

         // Remove group from JMX
         //
         try
         {
            log.debug ("Removing and JMX-unregistering an InvalidationGroup: " + groupName);
            javax.management.ObjectName groupObjectName = new javax.management.ObjectName (DEFAULT_INVALIDERS_JMX_NAME + ",GroupName="+groupName);
            this.getServer ().unregisterMBean (groupObjectName);      
         }
         catch (Exception e)
         {
            log.debug ("Problem while trying to un-register a new invalidation group in JMX", e);
         }

         // warn bridges
         //
         for (int i=0; i<bridgeSubscribers.size (); i++)
            ((BridgeInvalidationSubscriptionImpl)(bridgeSubscribers.elementAt (i))).groupDropped (groupName);
      }
               
   }
   
   protected synchronized void unregisterBridgeListener (BridgeInvalidationSubscription bridgeSubscriber)
   {
      // safe remove to avoid problems with iterators
      //
      log.debug ("Unsubscription of a cache-invalidation bridge");
      
      java.util.Vector newVector = new java.util.Vector (this.bridgeSubscribers);
      newVector.remove (bridgeSubscriber);
      this.bridgeSubscribers = newVector;      
   }
   
   protected void doLocalOnlyInvalidation (String groupName, Serializable key, boolean asynchronous)
   {
      InvalidationGroupImpl group = (InvalidationGroupImpl)this.groups.get (groupName);
      if (group != null)
         group.localOnlyInvalidate (key, asynchronous);      
   }
   
   protected void doLocalOnlyInvalidations (String groupName, Serializable[] keys, boolean asynchronous)
   {
      InvalidationGroupImpl group = (InvalidationGroupImpl)this.groups.get (groupName);
      if (group != null)
         group.localOnlyInvalidate (keys, asynchronous);      
   }

   protected void doLocalOnlyInvalidateAll (String groupName, boolean asynchronous)
   {
      InvalidationGroupImpl group = (InvalidationGroupImpl)this.groups.get (groupName);
      if (group != null)
         group.localOnlyInvalidateAll();
   }

   protected void doBridgedOnlyInvalidation (BridgeInvalidationSubscriptionImpl exceptSource, String groupName, Serializable key)
   {
      for (int i=0; i<bridgeSubscribers.size (); i++)
      {
         BridgeInvalidationSubscriptionImpl bridge = (BridgeInvalidationSubscriptionImpl)(bridgeSubscribers.elementAt (i));
         if (bridge != exceptSource)
            bridge.bridgedInvalidate (groupName, key, this.DEFAULT_TO_ASYNCHRONOUS_MODE);
      }
   }
   
   protected void doBridgedOnlyInvalidation (BridgeInvalidationSubscriptionImpl exceptSource, String groupName, Serializable[] keys)
   {
      for (int i=0; i<bridgeSubscribers.size (); i++)
      {
         BridgeInvalidationSubscriptionImpl bridge = (BridgeInvalidationSubscriptionImpl)(bridgeSubscribers.elementAt (i));
         if (bridge != exceptSource)
            bridge.bridgedInvalidate (groupName, keys, this.DEFAULT_TO_ASYNCHRONOUS_MODE);
      }
   }

   protected void doBridgedOnlyInvalidateAll (BridgeInvalidationSubscriptionImpl exceptSource, String groupName)
   {
      for (int i=0; i<bridgeSubscribers.size (); i++)
      {
         BridgeInvalidationSubscriptionImpl bridge = (BridgeInvalidationSubscriptionImpl)(bridgeSubscribers.elementAt (i));
         if (bridge != exceptSource)
            bridge.bridgedInvalidateAll (groupName, this.DEFAULT_TO_ASYNCHRONOUS_MODE);
      }
   }

   // this is called when an invalidation occurs in one of the group. Common behaviour
   // can be groupped here. By default, we simply forward the invalidations to the
   // available bridges.
   //
   protected void localGroupInvalidationEvent (String groupName, Serializable key, boolean asynchronous)
   {
      for (int i=0; i<bridgeSubscribers.size (); i++)
         ((BridgeInvalidationSubscriptionImpl)(bridgeSubscribers.elementAt (i))).bridgedInvalidate (groupName, key, asynchronous);      
   }
   
   protected void localGroupInvalidationsEvent (String groupName, Serializable[] keys, boolean asynchronous)
   {
      for (int i=0; i<bridgeSubscribers.size (); i++)
         ((BridgeInvalidationSubscriptionImpl)(bridgeSubscribers.elementAt (i))).bridgedInvalidate (groupName, keys, asynchronous);      
   }

   protected void localGroupInvalidateAllEvent (String groupName, boolean asynchronous)
   {
      for (int i=0; i<bridgeSubscribers.size (); i++)
         ((BridgeInvalidationSubscriptionImpl)(bridgeSubscribers.elementAt (i))).bridgedInvalidateAll (groupName, asynchronous);
   }

   // We warn other groups and the local group (if available)
   //
   protected void bridgeGroupInvalidationEvent (BridgeInvalidationSubscriptionImpl source, String groupName, Serializable key)
   {
      doBridgedOnlyInvalidation (source, groupName, key);      
      doLocalOnlyInvalidation (groupName, key, this.DEFAULT_TO_ASYNCHRONOUS_MODE);
   }
   
   protected void bridgeGroupInvalidationEvent (BridgeInvalidationSubscriptionImpl source, String groupName, Serializable[] keys)
   {
      doBridgedOnlyInvalidation (source, groupName, keys);      
      doLocalOnlyInvalidation (groupName, keys, this.DEFAULT_TO_ASYNCHRONOUS_MODE);
   }

   protected void bridgeGroupInvalidateAllEvent (BridgeInvalidationSubscriptionImpl source, String groupName)
   {
      doBridgedOnlyInvalidateAll (source, groupName);
      doLocalOnlyInvalidateAll (groupName, this.DEFAULT_TO_ASYNCHRONOUS_MODE);
   }

   protected void crossDomainBatchInvalidate (BridgeInvalidationSubscriptionImpl source, BatchInvalidation[] invalidations, boolean asynchronous)
   {
      if (invalidations == null)
         return;
      
      // local invalidation first
      //
      for (int i=0; i<invalidations.length; i++)
      {
         BatchInvalidation currInvalid = invalidations[i];
         
         doLocalOnlyInvalidations (currInvalid.getInvalidationGroupName (),
                                  currInvalid.getIds (), 
                                  asynchronous);
      }
      
      // bridged invalidation next
      //
      for (int i=0; i<bridgeSubscribers.size (); i++)
      {
         BridgeInvalidationSubscriptionImpl bridge = (BridgeInvalidationSubscriptionImpl)(bridgeSubscribers.elementAt (i));
         if (bridge != source)
            bridge.bridgedBatchInvalidations  (invalidations, asynchronous);
      }
   }

   protected void crossDomainInvalidateAll(BridgeInvalidationSubscriptionImpl source, String groupName, boolean asynchronous)
   {
      // local invalidation first
      //
      doLocalOnlyInvalidateAll(groupName, asynchronous);

      // bridged invalidation next
      //
      for (int i=0; i<bridgeSubscribers.size (); i++)
      {
         BridgeInvalidationSubscriptionImpl bridge = (BridgeInvalidationSubscriptionImpl)(bridgeSubscribers.elementAt (i));
         if (bridge != source)
            bridge.bridgedInvalidateAll(groupName, asynchronous);
      }
   }

   // Private -------------------------------------------------------
   
   // Inner classes -------------------------------------------------
   
   /**
    * This class implements the InvalidationGroup interface. It represent the 
    * meeting point of caches and invaliders for a same group.
    */
   class InvalidationGroupImpl implements InvalidationGroup, javax.management.DynamicMBean
   {
      protected org.jboss.logging.Logger igLog = null;      
      protected String groupName = null;
      protected boolean asynchronous = DEFAULT_TO_ASYNCHRONOUS_MODE;
      protected HashSet registered = new HashSet();
      protected int counter = 0;
      
      public int hashCode ()
      {
         return groupName.hashCode ();
      }
      
      public String getGroupName ()
      {
         return this.groupName;
      }
      
      public InvalidationGroupImpl (String groupName)
      {
         this.groupName = groupName;
         // JBAS-3924
         String escapedClass = getClass().getName().replace('$', '.'); 
         this.igLog = org.jboss.logging.Logger.getLogger(escapedClass + "." + groupName);
      }
      
      public InvalidationManagerMBean getInvalidationManager ()
      {
         return InvalidationManager.this;
      }

      public void invalidate (Serializable key)
      {
         this.invalidate (key, this.asynchronous);
      }
      
      public void invalidate (Serializable key, boolean asynchronous)
      {
         localOnlyInvalidate (key, asynchronous);
         
         localGroupInvalidationEvent (this.groupName, key, asynchronous);
      }

      public void invalidate (Serializable[] keys)
      {
         this.invalidate (keys, this.asynchronous);
      }
      
      public void invalidate (Serializable[] keys, boolean asynchronous)
      {
         localOnlyInvalidate (keys, asynchronous);
         
         localGroupInvalidationsEvent (this.groupName, keys, asynchronous);
      }

      public void invalidateAll()
      {
         invalidateAll(asynchronous);
      }

      public void invalidateAll(boolean asynchronous)
      {
         localOnlyInvalidateAll();
         localGroupInvalidateAllEvent(groupName, asynchronous);
      }

      public synchronized void register (Invalidatable newRegistered)
      {
         // we make a temp copy to avoid concurrency issues with the invalidate method
         //
         HashSet newlyRegistered = new HashSet (this.registered);         
         newlyRegistered.add (newRegistered);
         
         this.registered = newlyRegistered;                  
      }
      
      public synchronized void unregister (Invalidatable oldRegistered)
      {
         // we make a temp copy to avoid concurrency issues with the invalidate method
         //
         HashSet newlyRegistered = new HashSet(this.registered);         
         newlyRegistered.remove (oldRegistered);
         
         this.registered = newlyRegistered;
         
         this.removeReference ();
      }

      public void setAsynchronousInvalidation (boolean async)
      {
         this.asynchronous = async;
      }
      
      public boolean getAsynchronousInvalidation ()
      {
         return this.asynchronous;
      }
   
      public void addReference ()
      {         
         counter++;
         igLog.debug ("Counter reference value (++): " + counter);
      }
      
      public int getReferenceCount ()
      {
         return this.counter;
      }
      
      public void removeReference ()
      {
         counter--;
         igLog.debug ("Counter reference value (--): " + counter);
         
         if (counter<=0)
         {
            removeGroup (this.groupName);
                                 
            //Iterator iter = this.registered.iterator ();
            //while (iter.hasNext ())
            //   ((Invalidatable)iter.next ()).groupIsDropped () ;
         }
      }
      
      // DynamicMBean implementation ----------------------------------------------
      
      public Object getAttribute (String attribute) throws javax.management.AttributeNotFoundException, javax.management.MBeanException, javax.management.ReflectionException
      {
         if (attribute == null || attribute.equals (""))
            throw new IllegalArgumentException ("null or empty attribute name");
         
         if (attribute.equals ("AsynchronousInvalidation"))
            return new Boolean(this.asynchronous);
         else
            throw new javax.management.AttributeNotFoundException(attribute + " is not a known attribute");
      }
      
      public javax.management.AttributeList getAttributes (java.lang.String[] attributes)
      {
         return null;
      }
      
      public javax.management.MBeanInfo getMBeanInfo ()
      {
         
         MBeanParameterInfo serSimpleParam = new MBeanParameterInfo (
                  "key", 
                  Serializable.class.getName (), 
                  "Primary key to be invalidated"
         );
      
         MBeanParameterInfo serArrayParam = new MBeanParameterInfo (
                  "keys", 
                  Serializable[].class .getName (), 
                  "Primary keys to be invalidated"
         );
      
         MBeanParameterInfo asynchParam = new MBeanParameterInfo (
                  "asynchronous", 
                  Boolean.class.getName (), 
                  "Indicates if the invalidation should be asynchronous or must be synchronous"
         );
      
      javax.management.MBeanAttributeInfo[] attrInfo = new javax.management.MBeanAttributeInfo[] {
       new javax.management.MBeanAttributeInfo("AsynchronousInvalidation",
               Boolean.class.getName(),
               "Indicates if invalidation, by default, should be done asynchronously",
               true,
               true,
               false)};

         MBeanOperationInfo[] opInfo = {
            new MBeanOperationInfo("invalidate",
                                   "invalidate a single key using default (a)synchronous behaviour",
                                   new MBeanParameterInfo[] {serSimpleParam},
                                   void.class.getName(),
                                   MBeanOperationInfo.ACTION),

            new MBeanOperationInfo("invalidate",
                                   "invalidate a single key indicating the (a)synchronous behaviour",
                                   new MBeanParameterInfo[] {serSimpleParam, asynchParam},
                                   void.class.getName(),
                                   MBeanOperationInfo.ACTION),

            new MBeanOperationInfo("invalidate",
                                   "invalidate multiple keys using default (a)synchronous behaviour",
                                   new MBeanParameterInfo[] {serArrayParam},
                                   void.class.getName(),
                                   MBeanOperationInfo.ACTION),

            new MBeanOperationInfo("invalidate",
                                   "invalidate multiple keys indicating the (a)synchronous behaviour",
                                   new MBeanParameterInfo[] {serArrayParam, asynchParam},
                                   void.class.getName(),
                                   MBeanOperationInfo.ACTION),

            new MBeanOperationInfo("invalidateAll",
                                   "invalidate all keys using default (a)synchronous behaviour",
                                   new MBeanParameterInfo[] {},
                                   void.class.getName(),
                                   MBeanOperationInfo.ACTION),

            new MBeanOperationInfo("invalidateAll",
                                   "invalidate all keys with specified (a)synchronous behaviour",
                                   new MBeanParameterInfo[] {asynchParam},
                                   void.class.getName(),
                                   MBeanOperationInfo.ACTION)
         };

         javax.management.MBeanNotificationInfo[] notifyInfo = null;
         javax.management.MBeanConstructorInfo[] ctorInfo = new  javax.management.MBeanConstructorInfo[] {};

         return new javax.management.MBeanInfo(getClass().getName(),
                              "Cache invalidation for group named " + this.groupName,
                              attrInfo,
                              ctorInfo,
                              opInfo,
                              notifyInfo);
      }
      
      public java.lang.Object invoke (java.lang.String actionName, java.lang.Object[] params, java.lang.String[] signature) throws javax.management.MBeanException, javax.management.ReflectionException
      {
         if ("invalidate".equals (actionName))
         {
            if (params.length == 1)
            {
               if (params[0] instanceof Serializable[])
                  this.invalidate ((Serializable[])params[0]);
               else if (params[0] instanceof Serializable)
                  this.invalidate ((Serializable)params[0]);
               else
                  throw new IllegalArgumentException ("First argument must be Serializable (or array of)");
            }
            else if (params.length == 2)
            {
               if (params[0] instanceof Serializable[])
                  this.invalidate ((Serializable[])params[0], ((Boolean)params[1]).booleanValue ());
               else if (params[0] instanceof Serializable)
                  this.invalidate ((Serializable)params[0], ((Boolean)params[1]).booleanValue ());
               else
                  throw new IllegalArgumentException ("First argument must be Serializable (or array of)");
            }
            else
            {
               throw new IllegalArgumentException ("Unknown operation with these parameters: " + actionName);
            }
         }
         else if("invalidateAll".equals(actionName))
         {
            if(params == null || params.length == 0)
            {
               this.invalidateAll();
            }
            else if (params.length == 1)
            {
               this.invalidateAll (((Boolean)params[1]).booleanValue ());
            }
            else
            {
               throw new IllegalArgumentException ("invalidateAll can take zero or one parameter but got " + params.length);
            }
         }
         else
         {
            throw new IllegalArgumentException ("Unknown operation: " + actionName);
         }
         return null;
      }      
      
      public void setAttribute (javax.management.Attribute attribute) throws javax.management.AttributeNotFoundException, javax.management.InvalidAttributeValueException, javax.management.MBeanException, javax.management.ReflectionException
      {
         String attrName = attribute.getName();
        if (attrName == null || attrName.equals (""))
            throw new IllegalArgumentException ("null or empty attribute name");
         
         if (attrName.equals ("AsynchronousInvalidation"))
         {
            Object value = attribute.getValue ();
            if (value instanceof Boolean)
               this.asynchronous = ((Boolean)value).booleanValue ();
            else
               throw new javax.management.InvalidAttributeValueException("Attribute is of boolean type");
         }
         else
            throw new javax.management.AttributeNotFoundException(attrName + " is not a known attribute");
      }
      
      public javax.management.AttributeList setAttributes (javax.management.AttributeList attributes)
      {
         return null;
      }
      
      // Protected ------------------------------------------------------------------------
      
      protected void localOnlyInvalidate (Serializable[] keys, boolean asynchronous)
      {
         java.util.Iterator iter = this.registered.iterator ();
         while (iter.hasNext ())
         {
            Invalidatable inv = (Invalidatable)iter.next ();
            inv.areInvalid (keys);
         }
      }
      
      protected void localOnlyInvalidate (Serializable key, boolean asynchronous)
      {              
         java.util.Iterator iter = this.registered.iterator ();
         while (iter.hasNext ())
         {
            Invalidatable inv = (Invalidatable)iter.next ();
            inv.isInvalid (key);
         }

      }

      protected void localOnlyInvalidateAll()
      {
         java.util.Iterator iter = this.registered.iterator ();
         while (iter.hasNext ())
         {
            Invalidatable inv = (Invalidatable)iter.next ();
            inv.invalidateAll();
         }
      }
   }
   
   // *******************************************************************************************33
   // *******************************************************************************************33
   // *******************************************************************************************33
   
   class BridgeInvalidationSubscriptionImpl 
      implements BridgeInvalidationSubscription
   {
      
      protected InvalidationBridgeListener listener = null;
      
      public BridgeInvalidationSubscriptionImpl (InvalidationBridgeListener listener)
      {
         this.listener = listener;
      }
      
      public void invalidate (String invalidationGroupName, Serializable key)
      {
         bridgeGroupInvalidationEvent (this, invalidationGroupName, key);
      }
      
      public void invalidate (String invalidationGroupName, Serializable[] keys)
      {
         bridgeGroupInvalidationEvent (this, invalidationGroupName, keys);
      }

      public void invalidateAll(String groupName)
      {
         bridgeGroupInvalidateAllEvent(this, groupName);
      }

      public void batchInvalidate (BatchInvalidation[] invalidations)
      {
         crossDomainBatchInvalidate (this, invalidations, DEFAULT_TO_ASYNCHRONOUS_MODE);
         
      }
      
      public void unregister ()
      {
         unregisterBridgeListener (this);
      }
      
      // Internal callbacks
      //
      
      protected void bridgedInvalidate (String invalidationGroupName, Serializable key, boolean asynchronous)
      {
         this.listener.invalidate (invalidationGroupName, key, asynchronous);
      }
      
      protected void bridgedInvalidate (String invalidationGroupName, Serializable[] keys, boolean asynchronous)
      {
         this.listener.invalidate (invalidationGroupName, keys, asynchronous);
      }

      protected void bridgedInvalidateAll (String invalidationGroupName, boolean asynchronous)
      {
         this.listener.invalidateAll (invalidationGroupName, asynchronous);
      }
      
      protected void bridgedBatchInvalidations (BatchInvalidation[] invalidations, boolean asynchronous)
      {
         this.listener.batchInvalidate (invalidations, asynchronous);
      }
      
      protected void groupCreated (String invalidationGroupName)
      {
         this.listener.newGroupCreated (invalidationGroupName);
      }

      protected void groupDropped (String invalidationGroupName)
      {
         this.listener.groupIsDropped (invalidationGroupName);
      }
      
   }
}
