Package org.jboss.cache.notifications

Source Code of org.jboss.cache.notifications.Notifier

/*
* JBoss, Home of Professional Open Source
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.cache.notifications;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.Cache;
import org.jboss.cache.CacheException;
import org.jboss.cache.CacheSPI;
import org.jboss.cache.Fqn;
import org.jboss.cache.InvocationContext;
import org.jboss.cache.buddyreplication.BuddyGroup;
import org.jboss.cache.factories.annotations.Destroy;
import org.jboss.cache.factories.annotations.Inject;
import org.jboss.cache.marshall.MarshalledValueMap;
import org.jboss.cache.notifications.annotation.*;
import org.jboss.cache.notifications.event.*;
import static org.jboss.cache.notifications.event.Event.Type.*;
import org.jboss.cache.util.MapCopy;
import org.jgroups.View;

import javax.transaction.Transaction;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
* Helper class that handles all notifications to registered listeners.
*
* @author <a href="mailto:manik@jboss.org">Manik Surtani (manik@jboss.org)</a>
*/
public class Notifier
{
   private Cache cache;

   private static final Log log = LogFactory.getLog(Notifier.class);

   private static Class emptyMap = Collections.emptyMap().getClass();
   private static Class singletonMap = Collections.singletonMap(null, null).getClass();
   private static final Class[] allowedMethodAnnotations =
         {
               CacheStarted.class, CacheStopped.class, CacheBlocked.class, CacheUnblocked.class, NodeCreated.class, NodeRemoved.class, NodeVisited.class, NodeModified.class, NodeMoved.class,
               NodeActivated.class, NodePassivated.class, NodeLoaded.class, NodeEvicted.class, TransactionRegistered.class, TransactionCompleted.class, ViewChanged.class, BuddyGroupChanged.class
         };
   private static final Class[] parameterTypes =
         {
               CacheStartedEvent.class, CacheStoppedEvent.class, CacheBlockedEvent.class, CacheUnblockedEvent.class, NodeCreatedEvent.class, NodeRemovedEvent.class, NodeVisitedEvent.class, NodeModifiedEvent.class, NodeMovedEvent.class,
               NodeActivatedEvent.class, NodePassivatedEvent.class, NodeLoadedEvent.class, NodeEvictedEvent.class, TransactionRegisteredEvent.class, TransactionCompletedEvent.class, ViewChangedEvent.class, BuddyGroupChangedEvent.class
         };
   final Map<Class, List<ListenerInvocation>> listenerInvocations = new ConcurrentHashMap<Class, List<ListenerInvocation>>();

   public Notifier()
   {
   }

   public Notifier(Cache cache)
   {
      this.cache = cache;
   }

   @Inject
   private void injectDependencies(CacheSPI cache)
   {
      this.cache = cache;
   }

   @Destroy
   protected void destroy()
   {
      listenerInvocations.clear();
   }

   /**
    * Loops through all valid methods on the object passed in, and caches the relevant methods as {@link org.jboss.cache.notifications.Notifier.ListenerInvocation}
    * for invocation by reflection.
    *
    * @param listener object to be considered as a listener.
    */
   private void validateAndAddListenerInvocation(Object listener)
   {
      testListenerClassValidity(listener.getClass());

      boolean foundMethods = false;
      // now try all methods on the listener for anything that we like.  Note that only PUBLIC methods are scanned.
      for (Method m : listener.getClass().getMethods())
      {
         // loop through all valid method annotations
         for (int i = 0; i < allowedMethodAnnotations.length; i++)
         {
            if (m.isAnnotationPresent(allowedMethodAnnotations[i]))
            {
               testListenerMethodValidity(m, parameterTypes[i], allowedMethodAnnotations[i].getName());
               addListenerInvocation(allowedMethodAnnotations[i], new ListenerInvocation(listener, m));
               foundMethods = true;
            }
         }
      }

      if (!foundMethods && log.isWarnEnabled())
         log.warn("Attempted to register listener of class " + listener.getClass() + ", but no valid, public methods annotated with method-level event annotations found! Ignoring listener.");
   }

   private static void testListenerClassValidity(Class<?> listenerClass)
   {
      if (!listenerClass.isAnnotationPresent(CacheListener.class))
         throw new IncorrectCacheListenerException("Cache listener class MUST be annotated with org.jboss.cache.notifications.annotation.CacheListener");
      if (!Modifier.isPublic(listenerClass.getModifiers()))
         throw new IncorrectCacheListenerException("Cache listener class MUST be public!");
   }

   private static void testListenerMethodValidity(Method m, Class allowedParameter, String annotationName)
   {
      if (m.getParameterTypes().length != 1 || !m.getParameterTypes()[0].isAssignableFrom(allowedParameter))
         throw new IncorrectCacheListenerException("Methods annotated with " + annotationName + " must accept exactly one parameter, of assignable from type " + allowedParameter.getName());
      if (!m.getReturnType().equals(void.class))
         throw new IncorrectCacheListenerException("Methods annotated with " + annotationName + " should have a return type of void.");
   }

   private void addListenerInvocation(Class annotation, ListenerInvocation li)
   {
      synchronized (listenerInvocations)
      {
         List<ListenerInvocation> l = listenerInvocations.get(annotation);
         if (l == null)
         {
            l = new CopyOnWriteArrayList<ListenerInvocation>();
            listenerInvocations.put(annotation, l);
         }
         l.add(li);
      }
   }

   /**
    * Adds a cache listener to the list of cache listeners registered.
    *
    * @param listener
    */
   public void addCacheListener(Object listener)
   {
      validateAndAddListenerInvocation(listener);
   }

   /**
    * Removes a cache listener from the list of cache listeners registered.
    *
    * @param listener
    */
   public void removeCacheListener(Object listener)
   {
      synchronized (listenerInvocations)
      {
         for (Class annotation : allowedMethodAnnotations) removeListenerInvocation(annotation, listener);
      }
   }

   private void removeListenerInvocation(Class annotation, Object listener)
   {
      if (listener == null) return;

      List<ListenerInvocation> l = listenerInvocations.get(annotation);
      Set<Object> markedForRemoval = new HashSet<Object>();
      if (l != null)
      {
         for (ListenerInvocation li : l)
         {
            if (listener.equals(li.target)) markedForRemoval.add(li);
         }

         l.removeAll(markedForRemoval);

         if (l.isEmpty()) listenerInvocations.remove(annotation);
      }
   }

   /**
    * Removes all listeners from the notifier, including the evictionPolicyListener.
    */
   public void removeAllCacheListeners()
   {
      synchronized (listenerInvocations)
      {
         listenerInvocations.clear();
      }
   }

   /**
    * @return Retrieves an (unmodifiable) set of cache listeners registered.
    */
   public Set<Object> getCacheListeners()
   {
      Set<Object> s = new HashSet<Object>();
      synchronized (listenerInvocations)
      {
         for (Class annotation : allowedMethodAnnotations)
         {
            List<ListenerInvocation> l = listenerInvocations.get(annotation);
            if (l != null)
            {
               for (ListenerInvocation li : l) s.add(li.target);
            }
         }
      }
      return Collections.unmodifiableSet(s);
   }

   /**
    * Notifies all registered listeners of a nodeCreated event.
    *
    * @param fqn
    * @param pre
    * @param ctx context of invocation
    */
   public void notifyNodeCreated(Fqn fqn, boolean pre, InvocationContext ctx)
   {
      List<ListenerInvocation> listeners = listenerInvocations.get(NodeCreated.class);

      if (listeners != null && !listeners.isEmpty())
      {
         boolean originLocal = ctx.isOriginLocal();
         Transaction tx = ctx.getTransaction();
         InvocationContext backup = resetInvocationContext(ctx);
         EventImpl e = new EventImpl();
         e.setCache(cache);
         e.setOriginLocal(originLocal);
         e.setPre(pre);
         e.setFqn(fqn);
         e.setTransaction(tx);
         e.setType(NODE_CREATED);
         for (ListenerInvocation listener : listeners) listener.invoke(e);
         restoreInvocationContext(backup);
      }
   }

   /**
    * Notifies all registered listeners of a nodeModified event.
    *
    * @param fqn
    * @param pre
    * @param modificationType
    * @param data
    * @param ctx              context of invocation
    */
   public void notifyNodeModified(Fqn fqn, boolean pre, NodeModifiedEvent.ModificationType modificationType, Map data, InvocationContext ctx)
   {
      List<ListenerInvocation> listeners = listenerInvocations.get(NodeModified.class);

      if (listeners != null && !listeners.isEmpty())
      {
         boolean originLocal = ctx.isOriginLocal();
         Map dataCopy = copy(data);
         Transaction tx = ctx.getTransaction();
         InvocationContext backup = resetInvocationContext(ctx);
         EventImpl e = new EventImpl();
         e.setCache(cache);
         e.setOriginLocal(originLocal);
         e.setPre(pre);
         e.setFqn(fqn);
         e.setTransaction(tx);
         e.setModificationType(modificationType);
         e.setData(dataCopy);
         e.setType(NODE_MODIFIED);
         for (ListenerInvocation listener : listeners) listener.invoke(e);
         restoreInvocationContext(backup);
      }
   }

   /**
    * Notifies all registered listeners of a nodeRemoved event.
    *
    * @param fqn
    * @param pre
    * @param data
    * @param ctx  context of invocation
    */
   public void notifyNodeRemoved(Fqn fqn, boolean pre, Map data, InvocationContext ctx)
   {
      List<ListenerInvocation> listeners = listenerInvocations.get(NodeRemoved.class);

      if (listeners != null && !listeners.isEmpty())
      {
         boolean originLocal = ctx.isOriginLocal();
         Map dataCopy = copy(data);
         Transaction tx = ctx.getTransaction();
         InvocationContext backup = resetInvocationContext(ctx);
         EventImpl e = new EventImpl();
         e.setCache(cache);
         e.setOriginLocal(originLocal);
         e.setPre(pre);
         e.setFqn(fqn);
         e.setTransaction(tx);
         e.setData(dataCopy);
         e.setType(NODE_REMOVED);
         for (ListenerInvocation listener : listeners) listener.invoke(e);
         restoreInvocationContext(backup);
      }
   }

   /**
    * Notifies all registered listeners of a nodeVisited event.
    *
    * @param fqn
    * @param pre
    * @param ctx context of invocation
    */
   public void notifyNodeVisited(Fqn fqn, boolean pre, InvocationContext ctx)
   {
      List<ListenerInvocation> listeners = listenerInvocations.get(NodeVisited.class);

      if (listeners != null && !listeners.isEmpty())
      {
         Transaction tx = ctx.getTransaction();
         InvocationContext backup = resetInvocationContext(ctx);
         EventImpl e = new EventImpl();
         e.setCache(cache);
         e.setPre(pre);
         e.setFqn(fqn);
         e.setTransaction(tx);
         e.setType(NODE_VISITED);
         for (ListenerInvocation listener : listeners) listener.invoke(e);
         restoreInvocationContext(backup);
      }
   }

   public void notifyNodeMoved(Fqn originalFqn, Fqn newFqn, boolean pre, InvocationContext ctx)
   {
      List<ListenerInvocation> listeners = listenerInvocations.get(NodeMoved.class);

      if (listeners != null && !listeners.isEmpty())
      {
         boolean originLocal = ctx.isOriginLocal();
         Transaction tx = ctx.getTransaction();
         InvocationContext backup = resetInvocationContext(ctx);
         EventImpl e = new EventImpl();
         e.setCache(cache);
         e.setOriginLocal(originLocal);
         e.setPre(pre);
         e.setFqn(originalFqn);
         e.setTargetFqn(newFqn);
         e.setTransaction(tx);
         e.setType(NODE_MOVED);
         for (ListenerInvocation listener : listeners) listener.invoke(e);
         restoreInvocationContext(backup);
      }
   }


   /**
    * Notifies all registered listeners of a nodeEvicted event.
    *
    * @param fqn
    * @param pre
    * @param ctx context of invocation
    */
   public void notifyNodeEvicted(final Fqn fqn, final boolean pre, InvocationContext ctx)
   {
      List<ListenerInvocation> listeners = listenerInvocations.get(NodeEvicted.class);

      if (listeners != null && !listeners.isEmpty())
      {
         final boolean originLocal = ctx.isOriginLocal();
         Transaction tx = ctx.getTransaction();
         InvocationContext backup = resetInvocationContext(ctx);
         EventImpl e = new EventImpl();
         e.setCache(cache);
         e.setOriginLocal(originLocal);
         e.setPre(pre);
         e.setFqn(fqn);
         e.setTransaction(tx);
         e.setType(NODE_EVICTED);
         for (ListenerInvocation listener : listeners) listener.invoke(e);
         restoreInvocationContext(backup);
      }
   }

   /**
    * Notifies all registered listeners of a nodeLoaded event.
    *
    * @param fqn
    * @param pre
    * @param data
    * @param ctx  context of invocation
    */
   public void notifyNodeLoaded(Fqn fqn, boolean pre, Map data, InvocationContext ctx)
   {
      List<ListenerInvocation> listeners = listenerInvocations.get(NodeLoaded.class);

      if (listeners != null && !listeners.isEmpty())
      {
         boolean originLocal = ctx.isOriginLocal();
         Map dataCopy = copy(data);
         Transaction tx = ctx.getTransaction();
         InvocationContext backup = resetInvocationContext(ctx);
         EventImpl e = new EventImpl();
         e.setCache(cache);
         e.setOriginLocal(originLocal);
         e.setPre(pre);
         e.setFqn(fqn);
         e.setTransaction(tx);
         e.setData(dataCopy);
         e.setType(NODE_LOADED);
         for (ListenerInvocation listener : listeners) listener.invoke(e);
         restoreInvocationContext(backup);
      }
   }

   /**
    * Notifies all registered listeners of a nodeActivated event.
    *
    * @param fqn
    * @param pre
    * @param data
    * @param ctx  context of invocation
    */
   public void notifyNodeActivated(Fqn fqn, boolean pre, Map data, InvocationContext ctx)
   {
      List<ListenerInvocation> listeners = listenerInvocations.get(NodeActivated.class);

      if (listeners != null && !listeners.isEmpty())
      {
         boolean originLocal = ctx.isOriginLocal();
         Map dataCopy = copy(data);
         Transaction tx = ctx.getTransaction();
         InvocationContext backup = resetInvocationContext(ctx);
         EventImpl e = new EventImpl();
         e.setCache(cache);
         e.setOriginLocal(originLocal);
         e.setPre(pre);
         e.setFqn(fqn);
         e.setTransaction(tx);
         e.setData(dataCopy);
         e.setType(NODE_ACTIVATED);
         for (ListenerInvocation listener : listeners) listener.invoke(e);
         restoreInvocationContext(backup);
      }
   }

   /**
    * Notifies all registered listeners of a nodePassivated event.
    *
    * @param fqn
    * @param pre
    * @param data
    * @param ctx  context of invocation
    */
   public void notifyNodePassivated(Fqn fqn, boolean pre, Map data, InvocationContext ctx)
   {
      List<ListenerInvocation> listeners = listenerInvocations.get(NodePassivated.class);

      if (listeners != null && !listeners.isEmpty())
      {
         Map dataCopy = copy(data);
         Transaction tx = ctx.getTransaction();
         InvocationContext backup = resetInvocationContext(ctx);
         EventImpl e = new EventImpl();
         e.setCache(cache);
         e.setPre(pre);
         e.setFqn(fqn);
         e.setTransaction(tx);
         e.setData(dataCopy);
         e.setType(NODE_PASSIVATED);
         for (ListenerInvocation listener : listeners) listener.invoke(e);
         restoreInvocationContext(backup);
      }
   }

   /**
    * Notifies all registered listeners of a cacheStarted event.
    *
    * @param cache cache instance to notify
    * @param ctx   context of invocation
    */
   public void notifyCacheStarted(final CacheSPI cache, InvocationContext ctx)
   {
      List<ListenerInvocation> listeners = listenerInvocations.get(CacheStarted.class);

      if (listeners != null && !listeners.isEmpty())
      {
         InvocationContext backup = resetInvocationContext(ctx);
         EventImpl e = new EventImpl();
         e.setCache(cache);
         e.setType(CACHE_STARTED);
         for (ListenerInvocation listener : listeners) listener.invoke(e);
         restoreInvocationContext(backup);
      }
   }

   /**
    * Notifies all registered listeners of a cacheStopped event.
    *
    * @param cache cache instance to notify
    * @param ctx   context of invocation
    */
   public void notifyCacheStopped(final CacheSPI cache, InvocationContext ctx)
   {
      List<ListenerInvocation> listeners = listenerInvocations.get(CacheStopped.class);

      if (listeners != null && !listeners.isEmpty())
      {
         InvocationContext backup = resetInvocationContext(ctx);
         EventImpl e = new EventImpl();
         e.setCache(cache);
         e.setType(CACHE_STOPPED);
         for (ListenerInvocation listener : listeners) listener.invoke(e);
         restoreInvocationContext(backup);
      }
   }

   /**
    * Notifies all registered listeners of a viewChange event.  Note that viewChange notifications are ALWAYS sent
    * immediately.
    *
    * @param new_view
    * @param ctx      context of invocation
    */
   public void notifyViewChange(final View new_view, InvocationContext ctx)
   {
      List<ListenerInvocation> listeners = listenerInvocations.get(ViewChanged.class);

      if (listeners != null && !listeners.isEmpty())
      {
         InvocationContext backup = resetInvocationContext(ctx);
         EventImpl e = new EventImpl();
         e.setCache(cache);
         e.setNewView(new_view);
         e.setType(VIEW_CHANGED);
         for (ListenerInvocation listener : listeners) listener.invoke(e);
         restoreInvocationContext(backup);
      }
   }

   /**
    * Notifies all registered listeners of a buddy group change event.  Note that buddy group change notifications are ALWAYS sent
    * immediately.
    *
    * @param buddyGroup buddy group to set
    * @param pre        if true, this has occured before the buddy group message is broadcast to the cluster
    */
   public void notifyBuddyGroupChange(final BuddyGroup buddyGroup, boolean pre)
   {
      List<ListenerInvocation> listeners = listenerInvocations.get(BuddyGroupChanged.class);

      if (listeners != null && !listeners.isEmpty())
      {
//         InvocationContext backup = resetInvocationContext(ctx);
         EventImpl e = new EventImpl();
         e.setCache(cache);
         e.setBuddyGroup(buddyGroup);
         e.setPre(pre);
         e.setType(BUDDY_GROUP_CHANGED);
         for (ListenerInvocation listener : listeners) listener.invoke(e);
//         restoreInvocationContext(backup);
      }
   }

   /**
    * Notifies all registered listeners of a transaction completion event.
    *
    * @param transaction the transaction that has just completed
    * @param successful  if true, the transaction committed.  If false, this is a rollback event
    */
   public void notifyTransactionCompleted(Transaction transaction, boolean successful, InvocationContext ctx)
   {
      List<ListenerInvocation> listeners = listenerInvocations.get(TransactionCompleted.class);

      if (listeners != null && !listeners.isEmpty())
      {
         Transaction tx = ctx.getTransaction();
         boolean isOriginLocal = ctx.isOriginLocal();
         InvocationContext backup = resetInvocationContext(ctx);
         EventImpl e = new EventImpl();
         e.setCache(cache);
         e.setOriginLocal(isOriginLocal);
         e.setTransaction(tx);
         e.setSuccessful(successful);
         e.setType(TRANSACTION_COMPLETED);
         for (ListenerInvocation listener : listeners) listener.invoke(e);
         restoreInvocationContext(backup);
      }
   }

   /**
    * Notifies all registered listeners of a transaction registration event.
    *
    * @param transaction the transaction that has just completed
    */
   public void notifyTransactionRegistered(Transaction transaction, InvocationContext ctx)
   {
      List<ListenerInvocation> listeners = listenerInvocations.get(TransactionRegistered.class);

      if (listeners != null && !listeners.isEmpty())
      {
         Transaction tx = ctx.getTransaction();
         boolean isOriginLocal = ctx.isOriginLocal();
         InvocationContext backup = resetInvocationContext(ctx);
         EventImpl e = new EventImpl();
         e.setCache(cache);
         e.setOriginLocal(isOriginLocal);
         e.setTransaction(tx);
         e.setType(TRANSACTION_REGISTERED);
         for (ListenerInvocation listener : listeners) listener.invoke(e);
         restoreInvocationContext(backup);
      }
   }

   public void notifyCacheBlocked(CacheSPI cache, boolean pre)
   {
      List<ListenerInvocation> listeners = listenerInvocations.get(CacheBlocked.class);

      if (listeners != null && !listeners.isEmpty())
      {
         EventImpl e = new EventImpl();
         e.setCache(cache);
         e.setPre(pre);
         e.setType(CACHE_BLOCKED);
         for (ListenerInvocation listener : listeners) listener.invoke(e);
      }
   }

   public void notifyCacheUnblocked(CacheSPI cache, boolean pre)
   {
      List<ListenerInvocation> listeners = listenerInvocations.get(CacheUnblocked.class);

      if (listeners != null && !listeners.isEmpty())
      {
         EventImpl e = new EventImpl();
         e.setCache(cache);
         e.setPre(pre);
         e.setType(CACHE_UNBLOCKED);
         for (ListenerInvocation listener : listeners) listener.invoke(e);
      }
   }


   private static Map copy(Map data)
   {
      if (data == null) return null;
      if (data.isEmpty()) return Collections.emptyMap();
      if (safe(data)) return new MarshalledValueMap(data);
      return new MarshalledValueMap(new MapCopy(data));
   }

   private void restoreInvocationContext(InvocationContext backup)
   {
      cache.setInvocationContext(backup);
   }

   /**
    * Resets the current (passed-in) invocation, and returns a temp InvocationContext containing its state so it can
    * be restored later using {@link #restoreInvocationContext(org.jboss.cache.InvocationContext)}
    *
    * @param ctx the current context to be reset
    * @return a clone of ctx, before it was reset
    */
   private InvocationContext resetInvocationContext(InvocationContext ctx)
   {
      // wipe current context.
      cache.setInvocationContext(null);
      return ctx;
   }

   /**
    * A map is deemed 'safe' to be passed as-is to a listener, if either of the following are true:
    * <ul>
    * <li>It is null</li>
    * <li>It is an instance of {@link org.jboss.cache.util.MapCopy}, which is immutable</li>
    * <li>It is an instance of {@link java.util.Collections#emptyMap()}, which is also immutable</li>
    * <li>It is an instance of {@link java.util.Collections#singletonMap(Object,Object)}, which is also immutable</li>
    * </ul>
    *
    * @param map
    * @return
    */
   private static boolean safe(Map map)
   {
      return map == null || map instanceof MapCopy || map.getClass().equals(emptyMap) || map.getClass().equals(singletonMap);
   }

   /**
    * Class that encapsulates a valid invocation for a given registered listener - containing a reference to the
    * method to be invoked as well as the target object.
    */
   class ListenerInvocation
   {
      private Object target;
      private Method method;

      public ListenerInvocation(Object target, Method method)
      {
         this.target = target;
         this.method = method;
      }

      public void invoke(Event e)
      {
         try
         {
            method.invoke(target, e);
         }
         catch (InvocationTargetException e1)
         {
            Throwable cause = e1.getCause();
            if (cause != null)
               throw new CacheException("Caught exception invoking method " + method + " on listener instance " + target, cause);
            else
               throw new CacheException("Caught exception invoking method " + method + " on listener instance " + target, e1);
         }
         catch (IllegalAccessException e1)
         {
            log.warn("Unable to invoke method " + method + " on Object instance " + target + " - removing this target object from list of listeners!", e1);
            removeCacheListener(this.target);
         }
      }
   }

}
TOP

Related Classes of org.jboss.cache.notifications.Notifier

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.