/*
* 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.*;
import org.jboss.cache.buddyreplication.BuddyGroup;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.factories.annotations.*;
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.*;
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>
*/
@NonVolatile
public class NotifierImpl implements Notifier
{
private static final Log log = LogFactory.getLog(NotifierImpl.class);
private static final Class emptyMap = Collections.emptyMap().getClass();
private static final 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
};
private final List<ListenerInvocation> cacheStartedListeners = new CopyOnWriteArrayList<ListenerInvocation>();
private final List<ListenerInvocation> cacheStoppedListeners = new CopyOnWriteArrayList<ListenerInvocation>();
private final List<ListenerInvocation> cacheBlockedListeners = new CopyOnWriteArrayList<ListenerInvocation>();
private final List<ListenerInvocation> cacheUnblockedListeners = new CopyOnWriteArrayList<ListenerInvocation>();
private final List<ListenerInvocation> nodeCreatedListeners = new CopyOnWriteArrayList<ListenerInvocation>();
private final List<ListenerInvocation> nodeRemovedListeners = new CopyOnWriteArrayList<ListenerInvocation>();
private final List<ListenerInvocation> nodeVisitedListeners = new CopyOnWriteArrayList<ListenerInvocation>();
private final List<ListenerInvocation> nodeModifiedListeners = new CopyOnWriteArrayList<ListenerInvocation>();
private final List<ListenerInvocation> nodeMovedListeners = new CopyOnWriteArrayList<ListenerInvocation>();
private final List<ListenerInvocation> nodeActivatedListeners = new CopyOnWriteArrayList<ListenerInvocation>();
private final List<ListenerInvocation> nodePassivatedListeners = new CopyOnWriteArrayList<ListenerInvocation>();
private final List<ListenerInvocation> nodeLoadedListeners = new CopyOnWriteArrayList<ListenerInvocation>();
private final List<ListenerInvocation> nodeEvictedListeners = new CopyOnWriteArrayList<ListenerInvocation>();
private final List<ListenerInvocation> transactionRegisteredListeners = new CopyOnWriteArrayList<ListenerInvocation>();
private final List<ListenerInvocation> transactionCompletedListeners = new CopyOnWriteArrayList<ListenerInvocation>();
private final List<ListenerInvocation> viewChangedListeners = new CopyOnWriteArrayList<ListenerInvocation>();
private final List<ListenerInvocation> buddyGroupChangedListeners = new CopyOnWriteArrayList<ListenerInvocation>();
// final Map<Class, List<ListenerInvocation>> listenerInvocations = new ConcurrentHashMap<Class, List<ListenerInvocation>>();
private Cache cache;
private boolean useMarshalledValueMaps;
private Configuration config;
public NotifierImpl()
{
}
public NotifierImpl(Cache cache)
{
this.cache = cache;
}
@Inject
private void injectDependencies(CacheSPI cache, Configuration config)
{
this.cache = cache;
this.config = config;
}
@Destroy
protected void destroy()
{
removeAllCacheListeners();
}
@Start
protected void start()
{
useMarshalledValueMaps = config.isUseLazyDeserialization();
}
/**
* Loops through all valid methods on the object passed in, and caches the relevant methods as {@link NotifierImpl.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)
{
List<ListenerInvocation> result = getListenersForAnnotation(annotation);
result.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)
{
for (Class annotation : allowedMethodAnnotations) removeListenerInvocation(annotation, listener);
}
private void removeListenerInvocation(Class annotation, Object listener)
{
if (listener == null) return;
List<ListenerInvocation> l = getListenersForAnnotation(annotation);
Set<Object> markedForRemoval = new HashSet<Object>();
for (ListenerInvocation li : l)
{
if (listener.equals(li.target)) markedForRemoval.add(li);
}
l.removeAll(markedForRemoval);
}
/**
* Removes all listeners from the notifier, including the evictionPolicyListener.
*/
@Stop(priority = 99)
public void removeAllCacheListeners()
{
cacheStartedListeners.clear();
cacheStoppedListeners.clear();
cacheBlockedListeners.clear();
cacheUnblockedListeners.clear();
nodeCreatedListeners.clear();
nodeRemovedListeners.clear();
nodeVisitedListeners.clear();
nodeModifiedListeners.clear();
nodeMovedListeners.clear();
nodeActivatedListeners.clear();
nodePassivatedListeners.clear();
nodeLoadedListeners.clear();
nodeEvictedListeners.clear();
transactionRegisteredListeners.clear();
transactionCompletedListeners.clear();
viewChangedListeners.clear();
buddyGroupChangedListeners.clear();
}
/**
* @return Retrieves an (unmodifiable) set of cache listeners registered.
*/
public Set<Object> getCacheListeners()
{
Set<Object> result = new HashSet<Object>();
result.addAll(getListeningObjects(cacheStartedListeners));
result.addAll(getListeningObjects(cacheStoppedListeners));
result.addAll(getListeningObjects(cacheBlockedListeners));
result.addAll(getListeningObjects(cacheUnblockedListeners));
result.addAll(getListeningObjects(nodeCreatedListeners));
result.addAll(getListeningObjects(nodeRemovedListeners));
result.addAll(getListeningObjects(nodeVisitedListeners));
result.addAll(getListeningObjects(nodeModifiedListeners));
result.addAll(getListeningObjects(nodeMovedListeners));
result.addAll(getListeningObjects(nodeActivatedListeners));
result.addAll(getListeningObjects(nodePassivatedListeners));
result.addAll(getListeningObjects(nodeLoadedListeners));
result.addAll(getListeningObjects(nodeEvictedListeners));
result.addAll(getListeningObjects(transactionRegisteredListeners));
result.addAll(getListeningObjects(transactionCompletedListeners));
result.addAll(getListeningObjects(viewChangedListeners));
result.addAll(getListeningObjects(buddyGroupChangedListeners));
return Collections.unmodifiableSet(result);
}
public void notifyNodeCreated(Fqn fqn, boolean pre, InvocationContext ctx)
{
if (!nodeCreatedListeners.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 : nodeCreatedListeners) listener.invoke(e);
restoreInvocationContext(backup);
}
}
public void notifyNodeModified(Fqn fqn, boolean pre, NodeModifiedEvent.ModificationType modificationType, Map data, InvocationContext ctx)
{
if (!nodeModifiedListeners.isEmpty())
{
boolean originLocal = ctx.isOriginLocal();
Map dataCopy = copy(data, useMarshalledValueMaps);
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 : nodeModifiedListeners) listener.invoke(e);
restoreInvocationContext(backup);
}
}
public boolean shouldNotifyOnNodeModified()
{
return !nodeModifiedListeners.isEmpty();
}
public void notifyNodeRemoved(Fqn fqn, boolean pre, Map data, InvocationContext ctx)
{
if (!nodeRemovedListeners.isEmpty())
{
boolean originLocal = ctx.isOriginLocal();
Map dataCopy = copy(data, useMarshalledValueMaps);
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 : nodeRemovedListeners) listener.invoke(e);
restoreInvocationContext(backup);
}
}
public void notifyNodeVisited(Fqn fqn, boolean pre, InvocationContext ctx)
{
if (!nodeVisitedListeners.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 : nodeVisitedListeners) listener.invoke(e);
restoreInvocationContext(backup);
}
}
public void notifyNodeMoved(Fqn originalFqn, Fqn newFqn, boolean pre, InvocationContext ctx)
{
if (!nodeMovedListeners.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 : nodeMovedListeners) listener.invoke(e);
restoreInvocationContext(backup);
}
}
public void notifyNodeEvicted(final Fqn fqn, final boolean pre, InvocationContext ctx)
{
if (!nodeEvictedListeners.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 : nodeEvictedListeners) listener.invoke(e);
restoreInvocationContext(backup);
}
}
public void notifyNodeLoaded(Fqn fqn, boolean pre, Map data, InvocationContext ctx)
{
if (!nodeLoadedListeners.isEmpty())
{
boolean originLocal = ctx.isOriginLocal();
Map dataCopy = copy(data, useMarshalledValueMaps);
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 : nodeLoadedListeners) listener.invoke(e);
restoreInvocationContext(backup);
}
}
public void notifyNodeActivated(Fqn fqn, boolean pre, Map data, InvocationContext ctx)
{
if (!nodeActivatedListeners.isEmpty())
{
boolean originLocal = ctx.isOriginLocal();
Map dataCopy = copy(data, useMarshalledValueMaps);
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 : nodeActivatedListeners) listener.invoke(e);
restoreInvocationContext(backup);
}
}
public void notifyNodePassivated(Fqn fqn, boolean pre, Map data, InvocationContext ctx)
{
if (!nodePassivatedListeners.isEmpty())
{
Map dataCopy = copy(data, useMarshalledValueMaps);
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 : nodePassivatedListeners) listener.invoke(e);
restoreInvocationContext(backup);
}
}
/**
* Notifies all registered listeners of a cacheStarted event.
*/
@Start(priority = 99)
public void notifyCacheStarted()
{
if (!cacheStartedListeners.isEmpty())
{
EventImpl e = new EventImpl();
e.setCache(cache);
e.setType(CACHE_STARTED);
for (ListenerInvocation listener : cacheStartedListeners) listener.invoke(e);
}
}
/**
* Notifies all registered listeners of a cacheStopped event.
*/
@Stop(priority = 98)
public void notifyCacheStopped()
{
if (!cacheStoppedListeners.isEmpty())
{
EventImpl e = new EventImpl();
e.setCache(cache);
e.setType(CACHE_STOPPED);
for (ListenerInvocation listener : cacheStoppedListeners) listener.invoke(e);
}
}
public void notifyViewChange(final View newView, InvocationContext ctx)
{
if (!viewChangedListeners.isEmpty())
{
InvocationContext backup = resetInvocationContext(ctx);
EventImpl e = new EventImpl();
e.setCache(cache);
e.setNewView(newView);
e.setType(VIEW_CHANGED);
for (ListenerInvocation listener : viewChangedListeners) listener.invoke(e);
restoreInvocationContext(backup);
}
}
public void notifyBuddyGroupChange(final BuddyGroup buddyGroup, boolean pre)
{
if (!buddyGroupChangedListeners.isEmpty())
{
EventImpl e = new EventImpl();
e.setCache(cache);
e.setBuddyGroup(buddyGroup);
e.setPre(pre);
e.setType(BUDDY_GROUP_CHANGED);
for (ListenerInvocation listener : buddyGroupChangedListeners) listener.invoke(e);
}
}
public void notifyTransactionCompleted(Transaction transaction, boolean successful, InvocationContext ctx)
{
if (!transactionCompletedListeners.isEmpty())
{
boolean isOriginLocal = ctx.isOriginLocal();
InvocationContext backup = resetInvocationContext(ctx);
EventImpl e = new EventImpl();
e.setCache(cache);
e.setOriginLocal(isOriginLocal);
e.setTransaction(transaction);
e.setSuccessful(successful);
e.setType(TRANSACTION_COMPLETED);
for (ListenerInvocation listener : transactionCompletedListeners) listener.invoke(e);
restoreInvocationContext(backup);
}
}
public void notifyTransactionRegistered(Transaction transaction, InvocationContext ctx)
{
if (!transactionRegisteredListeners.isEmpty())
{
boolean isOriginLocal = ctx.isOriginLocal();
InvocationContext backup = resetInvocationContext(ctx);
EventImpl e = new EventImpl();
e.setCache(cache);
e.setOriginLocal(isOriginLocal);
e.setTransaction(transaction);
e.setType(TRANSACTION_REGISTERED);
for (ListenerInvocation listener : transactionRegisteredListeners) listener.invoke(e);
restoreInvocationContext(backup);
}
}
public void notifyCacheBlocked(boolean pre)
{
if (!cacheBlockedListeners.isEmpty())
{
EventImpl e = new EventImpl();
e.setCache(this.cache);
e.setPre(pre);
e.setType(CACHE_BLOCKED);
for (ListenerInvocation listener : cacheBlockedListeners) listener.invoke(e);
}
}
public void notifyCacheUnblocked(boolean pre)
{
if (!cacheUnblockedListeners.isEmpty())
{
EventImpl e = new EventImpl();
e.setCache(this.cache);
e.setPre(pre);
e.setType(CACHE_UNBLOCKED);
for (ListenerInvocation listener : cacheUnblockedListeners) listener.invoke(e);
}
}
private static Map copy(Map data, boolean useMarshalledValueMaps)
{
if (data == null) return null;
if (data.isEmpty()) return Collections.emptyMap();
if (safe(data)) return useMarshalledValueMaps ? new MarshalledValueMap(data) : data;
Map defensivelyCopiedData = new MapCopy(data);
return useMarshalledValueMaps ? new MarshalledValueMap(defensivelyCopiedData) : defensivelyCopiedData;
}
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 final Object target;
private final 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);
}
}
}
private List<ListenerInvocation> getListenersForAnnotation(Class annotation)
{
if (annotation == CacheStarted.class)
{
return cacheStartedListeners;
}
else if (annotation == CacheStopped.class)
{
return cacheStoppedListeners;
}
else if (annotation == CacheBlocked.class)
{
return cacheBlockedListeners;
}
else if (annotation == CacheUnblocked.class)
{
return cacheUnblockedListeners;
}
else if (annotation == NodeCreated.class)
{
return nodeCreatedListeners;
}
else if (annotation == NodeRemoved.class)
{
return nodeRemovedListeners;
}
else if (annotation == NodeVisited.class)
{
return nodeVisitedListeners;
}
else if (annotation == NodeModified.class)
{
return nodeModifiedListeners;
}
else if (annotation == NodeMoved.class)
{
return nodeMovedListeners;
}
else if (annotation == NodeActivated.class)
{
return nodeActivatedListeners;
}
else if (annotation == NodePassivated.class)
{
return nodePassivatedListeners;
}
else if (annotation == NodeLoaded.class)
{
return nodeLoadedListeners;
}
else if (annotation == NodeEvicted.class)
{
return nodeEvictedListeners;
}
else if (annotation == TransactionRegistered.class)
{
return transactionRegisteredListeners;
}
else if (annotation == TransactionCompleted.class)
{
return transactionCompletedListeners;
}
else if (annotation == ViewChanged.class)
{
return viewChangedListeners;
}
else if (annotation == BuddyGroupChanged.class)
{
return buddyGroupChangedListeners;
}
else
{
throw new RuntimeException("Unknown listener class: " + annotation);
}
}
private Collection getListeningObjects(List<ListenerInvocation> cacheStartedListeners)
{
Set result = new HashSet();
for (ListenerInvocation li : cacheStartedListeners)
{
result.add(li.target);
}
return result;
}
public List<ListenerInvocation> getCacheStartedListeners()
{
return cacheStartedListeners;
}
public List<ListenerInvocation> getCacheStoppedListeners()
{
return cacheStoppedListeners;
}
public List<ListenerInvocation> getCacheBlockedListeners()
{
return cacheBlockedListeners;
}
public List<ListenerInvocation> getCacheUnblockedListeners()
{
return cacheUnblockedListeners;
}
public List<ListenerInvocation> getNodeCreatedListeners()
{
return nodeCreatedListeners;
}
public List<ListenerInvocation> getNodeRemovedListeners()
{
return nodeRemovedListeners;
}
public List<ListenerInvocation> getNodeVisitedListeners()
{
return nodeVisitedListeners;
}
public List<ListenerInvocation> getNodeModifiedListeners()
{
return nodeModifiedListeners;
}
public List<ListenerInvocation> getNodeMovedListeners()
{
return nodeMovedListeners;
}
public List<ListenerInvocation> getNodeActivatedListeners()
{
return nodeActivatedListeners;
}
public List<ListenerInvocation> getNodePassivatedListeners()
{
return nodePassivatedListeners;
}
public List<ListenerInvocation> getNodeLoadedListeners()
{
return nodeLoadedListeners;
}
public List<ListenerInvocation> getNodeEvictedListeners()
{
return nodeEvictedListeners;
}
public List<ListenerInvocation> getTransactionRegisteredListeners()
{
return transactionRegisteredListeners;
}
public List<ListenerInvocation> getTransactionCompletedListeners()
{
return transactionCompletedListeners;
}
public List<ListenerInvocation> getViewChangedListeners()
{
return viewChangedListeners;
}
public List<ListenerInvocation> getBuddyGroupChangedListeners()
{
return buddyGroupChangedListeners;
}
}