View Javadoc

1   package com.atlassian.plugin.event.impl;
2   
3   import com.atlassian.plugin.event.PluginEventManager;
4   import com.atlassian.plugin.util.ClassUtils;
5   
6   import java.lang.reflect.Method;
7   import java.lang.reflect.InvocationTargetException;
8   import java.util.*;
9   import java.util.concurrent.atomic.AtomicBoolean;
10  
11  import org.apache.commons.collections.map.LazyMap;
12  import org.apache.commons.collections.Factory;
13  import org.apache.commons.logging.Log;
14  import org.apache.commons.logging.LogFactory;
15  import org.apache.commons.lang.Validate;
16  
17  /**
18   * Simple, synchronous event manager that uses one or more method selectors to determine event listeners.  The default
19   * method selectors are {@link MethodNameListenerMethodSelector} and {@link AnnotationListenerMethodSelector}.
20   */
21  public class DefaultPluginEventManager implements PluginEventManager
22  {
23      private final Map<Class,Set<Listener>> eventsToListener;
24      private static final Log log = LogFactory.getLog(DefaultPluginEventManager.class);
25      private final ListenerMethodSelector[] listenerMethodSelectors;
26  
27      /**
28       * Default constructor that looks for methods named "channel" and the @PluginEventListener annotations
29       */
30      public DefaultPluginEventManager()
31      {
32          this(new ListenerMethodSelector[]{new MethodNameListenerMethodSelector(), new AnnotationListenerMethodSelector()});
33      }
34  
35      /**
36       * Constructor that looks for an arbitrary selectors
37       * @param selectors List of selectors that determine which are listener methods
38       */
39      public DefaultPluginEventManager(ListenerMethodSelector[] selectors)
40      {
41          this.listenerMethodSelectors = selectors;
42          eventsToListener = LazyMap.decorate(new HashMap<Class,Set<Listener>>(), new Factory() {
43              public Set<Listener> create() { return new HashSet<Listener>(); }
44          });
45      }
46  
47      /**
48       * Broadcasts an event to all listeners synchronously, logging all exceptions as an ERROR.
49       *
50       * @param event The event object
51       */
52      public synchronized void broadcast(Object event)
53      {
54          Validate.notNull(event, "The event to broadcast must not be null");
55          final Set<Listener> calledListeners = new HashSet<Listener>();
56          for (Class type : ClassUtils.findAllTypes(event.getClass()))
57          {
58              Set<Listener> registrations = eventsToListener.get(type);
59              for (Listener reg : registrations)
60              {
61                  if (calledListeners.contains(reg))
62                      continue;
63                  calledListeners.add(reg);
64                  reg.notify(event);
65              }
66          }
67      }
68  
69      /**
70       * Registers a listener by scanning the object for all listener methods
71       *
72       * @param listener The listener object
73       * @throws IllegalArgumentException If the listener is null, contains a listener method with 0 or 2 or more
74       * arguments, or contains no listener methods
75       */
76      public synchronized void register(Object listener) throws IllegalArgumentException
77      {
78          if (listener == null)
79              throw new IllegalArgumentException("Listener cannot be null");
80  
81          final AtomicBoolean listenerFound = new AtomicBoolean(false);
82          forEveryListenerMethod(listener, new ListenerMethodHandler()
83          {
84              public void handle(Object listener, Method m)
85              {
86                  if (m.getParameterTypes().length != 1)
87                          throw new IllegalArgumentException("Listener methods must only have one argument");
88                  Set<Listener> listeners = eventsToListener.get(m.getParameterTypes()[0]);
89                  listeners.add(new Listener(listener, m));
90                  listenerFound.set(true);
91              }
92          });
93          if (!listenerFound.get())
94          {
95              throw new IllegalArgumentException("At least one listener method must be specified.  Most likely, a listener " +
96                  "method is missing the @PluginEventListener annotation.");
97          }
98      }
99  
100     /**
101      * Unregisters the listener
102      * @param listener The listener
103      */
104     public synchronized void unregister(Object listener)
105     {
106         forEveryListenerMethod(listener, new ListenerMethodHandler()
107         {
108             public void handle(Object listener, Method m)
109             {
110                 Set<Listener> listeners = eventsToListener.get(m.getParameterTypes()[0]);
111                 listeners.remove(new Listener(listener, m));
112             }
113         });
114     }
115 
116     /**
117      * Walks an object for every listener method and calls the handler
118      * @param listener The listener object
119      * @param handler The handler
120      */
121     void forEveryListenerMethod(Object listener, ListenerMethodHandler handler)
122     {
123         Method[] methods = listener.getClass().getMethods();
124         for (int x=0; x<methods.length; x++)
125         {
126             Method m = methods[x];
127             for (int s = 0; s<listenerMethodSelectors.length; s++)
128             {
129                 ListenerMethodSelector selector = listenerMethodSelectors[s];
130                 if (selector.isListenerMethod(m))
131                 {
132                     handler.handle(listener, m);
133                 }
134             }
135         }
136     }
137 
138     /**
139      * Records a registration of a listener method
140      */
141     /**
142      * Simple fake closure for logic that needs to execute for every listener method on an object
143      */
144     private static interface ListenerMethodHandler
145     {
146         void handle(Object listener, Method m);
147     }
148 
149     private static class Listener
150     {
151 
152         public final Object listener;
153 
154         public final Method method;
155 
156         public Listener(Object listener, Method method)
157         {
158             Validate.notNull(listener);
159             Validate.notNull(method);
160             this.listener = listener;
161             this.method = method;
162         }
163 
164         public void notify(Object event)
165         {
166             Validate.notNull(event);
167             try
168             {
169                 method.invoke(listener, event);
170             }
171             catch (IllegalAccessException e)
172             {
173                 log.error("Unable to access listener method: "+method, e);
174             }
175             catch (InvocationTargetException e)
176             {
177                 log.error("Exception calling listener method", e.getCause());
178             }
179         }
180 
181         public boolean equals(Object o)
182         {
183             if (this == o) return true;
184             if (o == null || getClass() != o.getClass()) return false;
185 
186             Listener that = (Listener) o;
187 
188             if (!listener.equals(that.listener)) return false;
189             if (!method.equals(that.method)) return false;
190 
191             return true;
192         }
193 
194         public int hashCode()
195         {
196             int result;
197             result = listener.hashCode();
198             result = 31 * result + method.hashCode();
199             return result;
200         }
201     }
202 }