View Javadoc
1   package com.atlassian.event.internal;
2   
3   import com.atlassian.event.api.EventPublisher;
4   import com.atlassian.event.config.ListenerHandlersConfiguration;
5   import com.atlassian.event.spi.EventDispatcher;
6   import com.atlassian.event.spi.ListenerHandler;
7   import com.atlassian.event.spi.ListenerInvoker;
8   import com.atlassian.plugin.eventlistener.descriptors.EventListenerModuleDescriptor;
9   import com.atlassian.plugin.scope.EverythingIsActiveScopeManager;
10  import com.atlassian.plugin.scope.ScopeManager;
11  import com.google.common.collect.Lists;
12  import com.google.common.collect.Maps;
13  import com.google.common.collect.Multimap;
14  import com.google.common.collect.Multimaps;
15  import com.google.common.collect.Sets;
16  import org.apache.commons.lang.builder.EqualsBuilder;
17  import org.apache.commons.lang.builder.HashCodeBuilder;
18  import org.slf4j.Logger;
19  import org.slf4j.LoggerFactory;
20  
21  import java.util.Collection;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Optional;
26  import java.util.Set;
27  import java.util.stream.Collectors;
28  
29  import static com.google.common.base.Preconditions.checkArgument;
30  import static com.google.common.base.Preconditions.checkNotNull;
31  import static com.google.common.collect.Sets.newLinkedHashSet;
32  import static org.apache.commons.lang.ObjectUtils.identityToString;
33  import static org.apache.commons.lang.StringUtils.isNotEmpty;
34  
35  /**
36   * <p>The default implementation of the {@link com.atlassian.event.api.EventPublisher} interface.</p>
37   * <p>
38   * <p>One can customise the event listening by instantiating with custom
39   * {@link com.atlassian.event.spi.ListenerHandler listener handlers} and the event dispatching through
40   * {@link com.atlassian.event.spi.EventDispatcher}. See the {@link com.atlassian.event.spi} package
41   * for more information.</p>
42   *
43   * @see com.atlassian.event.spi.ListenerHandler
44   * @see com.atlassian.event.spi.EventDispatcher
45   * @since 2.0
46   */
47  public final class EventPublisherImpl implements EventPublisher {
48      private static final Logger log = LoggerFactory.getLogger(EventPublisherImpl.class);
49  
50      private static final String PROPERTY_PREFIX = EventPublisherImpl.class.getName();
51      private static final Optional<String> debugRegistration =
52              Optional.ofNullable(System.getProperty(PROPERTY_PREFIX + ".debugRegistration"));
53      private static final boolean debugRegistrationLocation =
54              Boolean.getBoolean(PROPERTY_PREFIX + ".debugRegistrationLocation");
55      private static final Optional<String> debugInvocation =
56              Optional.ofNullable(System.getProperty(PROPERTY_PREFIX + ".debugInvocation"));
57      private static final boolean debugInvocationLocation =
58              Boolean.getBoolean(PROPERTY_PREFIX + ".debugInvocationLocation");
59  
60      private final EventDispatcher eventDispatcher;
61      private final List<ListenerHandler> listenerHandlers;
62      private final ScopeManager scopeManager;
63  
64      /**
65       * <strong>Note:</strong> this field makes this implementation stateful
66       */
67      private final Multimap<Class<?>, KeyedListenerInvoker> listenerInvokers;
68  
69      /**
70       * <p>If you need to customise the asynchronous handling, you should use the
71       * {@link com.atlassian.event.internal.AsynchronousAbleEventDispatcher} together with a custom executor. You might
72       * also want to have a look at using the {@link com.atlassian.event.internal.EventThreadFactory} to keep the naming
73       * of event threads consistent with the default naming of the Atlassian Event library.<p>
74       *
75       * @param eventDispatcher               the event dispatcher to be used with the publisher
76       * @param listenerHandlersConfiguration the list of listener handlers to be used with this publisher
77       * @see com.atlassian.event.internal.AsynchronousAbleEventDispatcher
78       * @see com.atlassian.event.internal.EventThreadFactory
79       */
80      public EventPublisherImpl(EventDispatcher eventDispatcher, ListenerHandlersConfiguration listenerHandlersConfiguration) {
81          this(eventDispatcher, listenerHandlersConfiguration, new EverythingIsActiveScopeManager());
82      }
83  
84      /**
85       * Inherits {@link EventPublisherImpl#EventPublisherImpl(EventDispatcher, ListenerHandlersConfiguration)} and
86       * allows injection of scope manager
87       *
88       * @param eventDispatcher               the event dispatcher to be used with the publisher
89       * @param listenerHandlersConfiguration the list of listener handlers to be used with this publisher
90       * @param scopeManager                  the scope manager
91       * @see com.atlassian.event.internal.AsynchronousAbleEventDispatcher
92       * @see com.atlassian.event.internal.EventThreadFactory
93       * @see ScopeManager
94       */
95      public EventPublisherImpl(EventDispatcher eventDispatcher,
96                                ListenerHandlersConfiguration listenerHandlersConfiguration,
97                                ScopeManager scopeManager) {
98          this.eventDispatcher = checkNotNull(eventDispatcher);
99          this.listenerHandlers = checkNotNull(checkNotNull(listenerHandlersConfiguration).getListenerHandlers());
100         this.listenerInvokers = newMultimap();
101         this.scopeManager = checkNotNull(scopeManager);
102     }
103 
104     /**
105      * The order in which registered listeners are invoked is predictable. Listeners will be invoked for listeners registered
106      * on the object itself, then listeners on the parent class, then the grandparent and so on until finally all listeners for java.lang.Object are invoked.
107      * After walking the class hierarchy the interface listeners are invoked, again from the most specific interface first.  Note that the ordering within a specific
108      * event type is not guaranteed.  If there are multiple registered listeners for IssueEvent, then they will be invoked in the order of registration.
109      * It is however guaranteed that a listener for IssueEvent will be invoked before a listener for Event
110      *
111      * @param event the event to publish
112      */
113     public void publish(Object event) {
114         invokeListeners(findListenerInvokersForEvent(checkNotNull(event)), event);
115     }
116 
117     public void register(final Object listener) {
118         registerListener(identityToString(checkNotNull(listener)), listener);
119     }
120 
121     public void unregister(Object listener) {
122         unregisterListener(identityToString(checkNotNull(listener)));
123     }
124 
125     public void unregisterAll() {
126         synchronized (listenerInvokers) {
127             listenerInvokers.clear();
128         }
129     }
130 
131     private void unregisterListener(String listenerKey) {
132         checkArgument(isNotEmpty(listenerKey), "Key for the listener must not be empty");
133 
134         /** see {@link Multimaps#synchronizedMultimap(Multimap)} for why this synchronize block is there */
135         synchronized (listenerInvokers) {
136             for (Iterator<Map.Entry<Class<?>, KeyedListenerInvoker>> invokerIterator = listenerInvokers.entries().iterator(); invokerIterator.hasNext(); ) {
137                 if (invokerIterator.next().getValue().getKey().equals(listenerKey)) {
138                     invokerIterator.remove();
139                 }
140             }
141         }
142     }
143 
144     private void registerListener(String listenerKey, Object listener) {
145         final Object listenerImpl;
146         final Optional<String> parentScope;
147         if (listener instanceof EventListenerModuleDescriptor) {
148             final EventListenerModuleDescriptor descriptor = (EventListenerModuleDescriptor) listener;
149             listenerImpl = descriptor.getModule();
150             parentScope = descriptor.getScopeKey();
151         } else {
152             listenerImpl = listener;
153             parentScope = Optional.empty();
154         }
155 
156         synchronized (listenerInvokers) /* Because we need to un-register an re-register in one 'atomic' operation */ {
157             unregisterListener(listenerKey);
158 
159             final List<ListenerInvoker> invokers = Lists.newArrayList();
160             for (ListenerHandler listenerHandler : listenerHandlers) {
161                 invokers.addAll(listenerHandler.getInvokers(listenerImpl));
162             }
163             if (!invokers.isEmpty()) {
164                 registerListenerInvokers(listenerKey, parentScope, invokers);
165             } else {
166                 throw new IllegalArgumentException("No listener invokers were found for listener <" + listenerImpl + ">");
167             }
168         }
169     }
170 
171     private Set<KeyedListenerInvoker> findListenerInvokersForEvent(Object event) {
172         final Set<KeyedListenerInvoker> invokers = newLinkedHashSet();
173         /** see {@link Multimaps#synchronizedMultimap(Multimap)} for why this synchronize block is there */
174         synchronized (listenerInvokers) {
175             for (Class<?> eventClass : ClassUtils.findAllTypes(checkNotNull(event).getClass())) {
176                 invokers.addAll(listenerInvokers.get(eventClass));
177             }
178         }
179 
180         final List<KeyedListenerInvoker> activeInvokers = invokers
181                 .stream()
182                 .filter(i -> i.getScope()
183                         .map(s -> scopeManager.isScopeActive(s))
184                         .orElse(true))
185                 .collect(Collectors.toList());
186 
187         return newLinkedHashSet(activeInvokers);
188     }
189 
190     private void invokeListeners(Collection<KeyedListenerInvoker> listenerInvokers, Object event) {
191         final String eventClass = event.getClass().getName();
192         final boolean debugThisInvocation = debugInvocation.map(eventClass::startsWith).orElse(false);
193         for (KeyedListenerInvoker keyedInvoker : listenerInvokers) {
194             final ListenerInvoker invoker = keyedInvoker.getInvoker();
195             if (debugThisInvocation) {
196                 log.warn("Listener invoked event with class '{}' -> invoker {}", eventClass, invoker);
197                 if (debugInvocationLocation) {
198                     log.warn("Invoked from", new Exception());
199                 }
200             }
201             // EVENT-14 -  we should continue to process all listeners even if one throws some horrible exception
202             try {
203                 eventDispatcher.dispatch(invoker, event);
204             } catch (Exception e) {
205                 log.error("There was an exception thrown trying to dispatch event '{}' from the invoker '{}'.",
206                         event, invoker, e);
207             }
208         }
209     }
210 
211     private void registerListenerInvokers(String listenerKey, Optional<String> parentScope, List<? extends ListenerInvoker> invokers) {
212         for (ListenerInvoker invoker : invokers) {
213             registerListenerInvoker(listenerKey, parentScope, invoker);
214         }
215     }
216 
217     private void registerListenerInvoker(String listenerKey, Optional<String> parentScope, ListenerInvoker invoker) {
218         // if supported classes is empty, then all events are supported.
219         if (invoker.getSupportedEventTypes().isEmpty()) {
220             putEventListenerInvoker(Object.class, listenerKey, parentScope, invoker);
221         }
222 
223         // if it it empty, we won't loop, otherwise register the invoker against all its classes
224         for (Class<?> eventClass : invoker.getSupportedEventTypes()) {
225             putEventListenerInvoker(eventClass, listenerKey, parentScope, invoker);
226         }
227     }
228 
229     private void putEventListenerInvoker(final Class<?> eventClass, String listenerKey, Optional<String> parentScope, ListenerInvoker invoker) {
230         debugRegistration.ifPresent(classPrefix -> {
231             if (eventClass.getName().startsWith(classPrefix)) {
232                 log.warn("Listener registered event '{}' -> invoker {}", eventClass, invoker);
233                 if (debugRegistrationLocation) {
234                     log.warn("Registered from", new Exception());
235                 }
236             }
237         });
238 
239         log.debug("Registering {} with scope {}", listenerKey, parentScope);
240 
241         if(parentScope.isPresent() && invoker.getScope().isPresent() && !parentScope.equals(invoker.getScope()))
242         {
243             throw new IllegalArgumentException("Listener <" + listenerKey + "> tries to override parent scope <" + parentScope.get() + "> with <" + invoker.getScope() + ">");
244         }
245 
246         final Optional<String> invokerScope = parentScope.isPresent()
247                 ?parentScope
248                 :invoker.getScope();
249 
250         listenerInvokers.put(eventClass, new KeyedListenerInvoker(listenerKey, invoker, invokerScope));
251     }
252 
253     private Multimap<Class<?>, KeyedListenerInvoker> newMultimap() {
254         return Multimaps.synchronizedMultimap(
255                 Multimaps.newMultimap(Maps.newHashMap(), () -> Sets.newHashSet()));
256     }
257 
258     private static final class KeyedListenerInvoker {
259         private final String key;
260         private final ListenerInvoker invoker;
261         private final Optional<String> scope;
262 
263         KeyedListenerInvoker(String key, ListenerInvoker invoker, Optional<String> scope) {
264             this.invoker = invoker;
265             this.key = key;
266             this.scope = scope;
267         }
268 
269         String getKey() {
270             return key;
271         }
272 
273         ListenerInvoker getInvoker() {
274             return invoker;
275         }
276 
277         Optional<String> getScope() {
278             return scope;
279         }
280 
281         @Override
282         public int hashCode() {
283             return new HashCodeBuilder(5, 23).append(key).append(invoker).toHashCode();
284         }
285 
286         @Override
287         public boolean equals(Object obj) {
288             if (obj == this) {
289                 return true;
290             }
291             if (obj == null || obj.getClass() != getClass()) {
292                 return false;
293             }
294             final KeyedListenerInvoker kli = (KeyedListenerInvoker) obj;
295             return new EqualsBuilder()
296                     .append(key, kli.key)
297                     .append(invoker, kli.invoker)
298                     .append(scope, kli.scope)
299                     .isEquals();
300         }
301     }
302 }