1. Project Clover database Sat Feb 2 2019 06:45:20 CET
  2. Package org.xwiki.observation.internal

File DefaultObservationManager.java

 

Coverage histogram

../../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

42
87
17
2
400
236
41
0.47
5.12
8.5
2.41

Classes

Class Line # Actions
DefaultObservationManager 55 83 0% 38 6
0.9568345595.7%
DefaultObservationManager.RegisteredListener 83 4 0% 3 0
1.0100%
 

Contributing tests

This file is covered by 297 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.observation.internal;
21   
22    import java.util.ArrayList;
23    import java.util.Collection;
24    import java.util.List;
25    import java.util.Map;
26    import java.util.concurrent.ConcurrentHashMap;
27   
28    import javax.inject.Inject;
29    import javax.inject.Singleton;
30   
31    import org.slf4j.Logger;
32    import org.xwiki.component.annotation.Component;
33    import org.xwiki.component.descriptor.ComponentDescriptor;
34    import org.xwiki.component.event.ComponentDescriptorAddedEvent;
35    import org.xwiki.component.event.ComponentDescriptorEvent;
36    import org.xwiki.component.event.ComponentDescriptorRemovedEvent;
37    import org.xwiki.component.manager.ComponentLookupException;
38    import org.xwiki.component.manager.ComponentManager;
39    import org.xwiki.observation.EventListener;
40    import org.xwiki.observation.ObservationManager;
41    import org.xwiki.observation.event.AllEvent;
42    import org.xwiki.observation.event.Event;
43   
44    /**
45    * Default implementation of the {@link ObservationManager}.
46    * <p>
47    * This component use synchronized for concurrent protection instead of having
48    * {@link java.util.concurrent.ConcurrentHashMap} everywhere because it's more efficient since most of methods access to
49    * several maps and generally do enumerations.
50    *
51    * @version $Id: 50d3026235640acbb5cd5a48365fc05585ec2962 $
52    */
53    @Component
54    @Singleton
 
55    public class DefaultObservationManager implements ObservationManager
56    {
57    /**
58    * @see #getListenersByEvent()
59    */
60    private volatile Map<Class<? extends Event>, Map<String, RegisteredListener>> listenersByEvent;
61   
62    /**
63    * @see #getListenersByName()
64    */
65    private volatile Map<String, EventListener> listenersByName;
66   
67    /**
68    * Used to find all components implementing {@link EventListener} to register them automatically.
69    */
70    @Inject
71    private ComponentManager componentManager;
72   
73    /**
74    * The logger to log.
75    */
76    @Inject
77    private Logger logger;
78   
79    /**
80    * Helper class to store the list of events of a given type associated with a given listener. We need this for
81    * performance reasons and also in order to be able to add events after a listener has been registered.
82    */
 
83    private static class RegisteredListener
84    {
85    /**
86    * Events of a given type associated with a given listener.
87    */
88    private List<Event> events = new ArrayList<>();
89   
90    /**
91    * Listener associated with the events.
92    */
93    private EventListener listener;
94   
95    /**
96    * @param listener the listener associated with the events.
97    * @param event the first event to associate with the passed listener. More events are added by calling
98    * {@link #addEvent(Event)}
99    */
 
100  197876 toggle RegisteredListener(EventListener listener, Event event)
101    {
102  197876 addEvent(event);
103   
104  197876 this.listener = listener;
105    }
106   
107    /**
108    * @param event the event to add
109    */
 
110  198232 toggle void addEvent(Event event)
111    {
112  198232 this.events.add(event);
113    }
114   
115    /**
116    * @param event the event to remove
117    */
 
118  1 toggle void removeEvent(Event event)
119    {
120  1 this.events.remove(event);
121    }
122    }
123   
124    /**
125    * @return the registered listeners indexed on Event classes so that it's fast to find all the listeners registered
126    * for a given event, so that {@link #notify} calls execute fast and in a fixed amount a time.
127    */
 
128  6953022 toggle private Map<Class<? extends Event>, Map<String, RegisteredListener>> getListenersByEvent()
129    {
130  6953416 if (this.listenersByEvent == null) {
131  298 initializeListeners();
132    }
133   
134  6953567 return this.listenersByEvent;
135    }
136   
137    /**
138    * @return the registered listeners index by listener name. It makes it fast to perform operations on already
139    * registered listeners.
140    */
 
141  98424 toggle private Map<String, EventListener> getListenersByName()
142    {
143  98432 if (this.listenersByName == null) {
144  91 initializeListeners();
145    }
146   
147  98429 return this.listenersByName;
148    }
149   
150    /**
151    * Lazily initialized to allow @Inject {@link ObservationManager} in a listener.
152    *
153    * @todo Should we allow event inheritance ?
154    */
 
155  389 toggle private synchronized void initializeListeners()
156    {
157  389 if (this.listenersByName == null) {
158  389 this.listenersByEvent = new ConcurrentHashMap<>();
159  389 this.listenersByName = new ConcurrentHashMap<>();
160   
161    // Can be null in unit tests
162  389 if (this.componentManager != null) {
163  389 try {
164  389 for (EventListener listener : this.componentManager
165    .<EventListener>getInstanceList(EventListener.class)) {
166  7835 addListener(listener);
167    }
168    } catch (ComponentLookupException e) {
169  1 this.logger.error("Failed to lookup listeners", e);
170    }
171    }
172    }
173    }
174   
 
175  53941 toggle @Override
176    public void addListener(EventListener eventListener)
177    {
178  53945 Map<String, EventListener> listeners = getListenersByName();
179   
180    // Remove previous listener if any
181  53945 EventListener previousListener = listeners.get(eventListener.getName());
182  53950 if (previousListener != null) {
183  8 removeListener(eventListener.getName());
184   
185  8 this.logger.warn(
186    "The [{}] listener is overwriting a previously "
187    + "registered listener [{}] since they both are registered under the same id [{}]. "
188    + "In the future consider removing a Listener first if you really want to register it again.",
189    new Object[] { eventListener.getClass().getName(), previousListener.getClass().getName(),
190    eventListener.getName() });
191    }
192   
193    // Register the listener by name. If already registered, override it.
194  53950 listeners.put(eventListener.getName(), eventListener);
195   
196    // when lot of threads are involved there might be a concurrent access when inserting a new listener
197    // this needs to be managed with a lock to avoid an event to be "lost", e.g. not consumed by the appropriate
198    // listener
199  53948 synchronized (this.listenersByEvent) {
200    // For each event defined for this listener, add it to the Event Map.
201  53955 for (Event event : eventListener.getEvents()) {
202    // Check if this is a new Event type not already registered
203  198229 Map<String, RegisteredListener> eventListeners = this.listenersByEvent.get(event.getClass());
204  198229 if (eventListeners == null) {
205    // No listener registered for this event yet. Create a map to store listeners for this event.
206  95586 eventListeners = new ConcurrentHashMap<String, RegisteredListener>();
207  95586 this.listenersByEvent.put(event.getClass(), eventListeners);
208    // There is no RegisteredListener yet, create one
209  95586 eventListeners.put(eventListener.getName(), new RegisteredListener(eventListener, event));
210    } else {
211    // Add an event to existing RegisteredListener object
212  102643 RegisteredListener registeredListener = eventListeners.get(eventListener.getName());
213  102643 if (registeredListener == null) {
214  102289 eventListeners.put(eventListener.getName(), new RegisteredListener(eventListener, event));
215    } else {
216  354 registeredListener.addEvent(event);
217    }
218    }
219    }
220    }
221    }
222   
 
223  44410 toggle @Override
224    public void removeListener(String listenerName)
225    {
226  44412 getListenersByName().remove(listenerName);
227  44410 for (Map.Entry<Class<? extends Event>, Map<String, RegisteredListener>> entry : this.listenersByEvent
228    .entrySet()) {
229  2633262 entry.getValue().remove(listenerName);
230  2633278 if (entry.getValue().isEmpty()) {
231  87630 this.listenersByEvent.remove(entry.getKey());
232    }
233    }
234    }
235   
 
236  3 toggle @Override
237    public void addEvent(String listenerName, Event event)
238    {
239  3 Map<String, RegisteredListener> listeners = getListenersByEvent().get(event.getClass());
240  3 if (listeners == null) {
241  1 listeners = new ConcurrentHashMap<>();
242  1 this.listenersByEvent.put(event.getClass(), listeners);
243    }
244  3 RegisteredListener listener = listeners.get(listenerName);
245  3 if (listener != null) {
246  2 listener.addEvent(event);
247    } else {
248  1 listeners.put(listenerName, new RegisteredListener(this.getListener(listenerName), event));
249    }
250    }
251   
 
252  1 toggle @Override
253    public void removeEvent(String listenerName, Event event)
254    {
255  1 Map<String, RegisteredListener> listeners = getListenersByEvent().get(event.getClass());
256  1 RegisteredListener listener = listeners.get(listenerName);
257  1 if (listener != null) {
258  1 listener.removeEvent(event);
259    }
260    }
261   
 
262  61 toggle @Override
263    public EventListener getListener(String listenerName)
264    {
265  61 return getListenersByName().get(listenerName);
266    }
267   
 
268  6951980 toggle @Override
269    public void notify(Event event, Object source, Object data)
270    {
271    // Find all listeners for this event
272  6953206 Map<String, RegisteredListener> regListeners = getListenersByEvent().get(event.getClass());
273  6952355 if (regListeners != null) {
274  1458947 notify(regListeners.values(), event, source, data);
275    }
276   
277    // Find listener listening all events
278  6953780 Map<String, RegisteredListener> allEventRegListeners = this.listenersByEvent.get(AllEvent.class);
279  6954014 if (allEventRegListeners != null) {
280  6953407 notify(allEventRegListeners.values(), event, source, data);
281    }
282   
283    // We want this Observation Manager to be able to handle new Event Listener components being added or removed
284    // at runtime. Thus ideally we should make this Manager an Event Listener itself. However in order to avoid
285    // circular dependencies issues and in order to be more performant we simply handle ComponentDescriptorEvents
286    // here to add/remove Event Listeners.
287  6953715 if (event instanceof ComponentDescriptorEvent) {
288  8384 onComponentEvent((ComponentDescriptorEvent) event, (ComponentManager) source,
289    (ComponentDescriptor<EventListener>) data);
290    }
291    }
292   
293    /**
294    * Call the provided listeners matching the passed Event. The definition of <em>source</em> and <em>data</em> is
295    * purely up to the communicating classes.
296    *
297    * @param listeners the listeners to notify
298    * @param event the event to pass to the registered listeners
299    * @param source the source of the event (or <code>null</code>)
300    * @param data the additional data related to the event (or <code>null</code>)
301    */
 
302  8412087 toggle private void notify(Collection<RegisteredListener> listeners, Event event, Object source, Object data)
303    {
304  8413073 for (RegisteredListener listener : listeners) {
305    // Verify that one of the events matches and send the first matching event
306  22828196 for (Event listenerEvent : listener.events) {
307  22937046 if (listenerEvent.matches(event)) {
308  22478649 try {
309  22480329 listener.listener.onEvent(event, source, data);
310    } catch (Exception e) {
311    // protect from bad listeners
312  23 this.logger.error("Failed to send event [{}] to listener [{}]", new Object[] { event,
313    listener.listener, e });
314    }
315   
316    // Only send the first matching event since the listener should only be called once per event.
317  22480081 break;
318    }
319    }
320    }
321    }
322   
 
323  2919259 toggle @Override
324    public void notify(Event event, Object source)
325    {
326  2919387 notify(event, source, null);
327    }
328   
329    /**
330    * A Component has been modified (added or removed) and we update our cache of Event Listeners if that Component is
331    * an Event Listener.
332    *
333    * @param componentEvent the event about the Component being added or removed
334    * @param componentManager the {@link ComponentManager} where the descriptor is registered
335    * @param descriptor the descriptor of the modified component
336    */
 
337  8384 toggle private void onComponentEvent(ComponentDescriptorEvent componentEvent, ComponentManager componentManager,
338    ComponentDescriptor<EventListener> descriptor)
339    {
340  8384 if (componentEvent.getRoleType() == EventListener.class) {
341  64 if (componentEvent instanceof ComponentDescriptorAddedEvent) {
342  43 onEventListenerComponentAdded((ComponentDescriptorAddedEvent) componentEvent, componentManager,
343    descriptor);
344  21 } else if (componentEvent instanceof ComponentDescriptorRemovedEvent) {
345  21 onEventListenerComponentRemoved((ComponentDescriptorRemovedEvent) componentEvent, componentManager,
346    descriptor);
347    } else {
348  0 this.logger.warn("Ignoring unknown Component event [{}]", componentEvent.getClass().getName());
349    }
350    }
351    }
352   
353    /**
354    * An Event Listener Component has been dynamically registered in the system, add it to our cache.
355    *
356    * @param event event object containing the new component descriptor
357    * @param componentManager the {@link ComponentManager} where the descriptor is registered
358    * @param descriptor the component descriptor removed from component manager
359    */
 
360  43 toggle private void onEventListenerComponentAdded(ComponentDescriptorAddedEvent event, ComponentManager componentManager,
361    ComponentDescriptor<EventListener> descriptor)
362    {
363  43 try {
364  43 EventListener eventListener = componentManager.getInstance(EventListener.class, event.getRoleHint());
365   
366  43 if (getListener(eventListener.getName()) != eventListener) {
367  41 addListener(eventListener);
368    } else {
369  2 this.logger.warn("An Event Listener named [{}] already exists, ignoring the [{}] component",
370    eventListener.getName(), descriptor.getImplementation().getName());
371    }
372    } catch (ComponentLookupException e) {
373  0 this.logger.error("Failed to lookup the Event Listener [{}] corresponding to the Component registration "
374    + "event for [{}]. Ignoring the event", new Object[] { event.getRoleHint(),
375    descriptor.getImplementation().getName(), e });
376    }
377    }
378   
379    /**
380    * An Event Listener Component has been dynamically unregistered in the system, remove it from our cache.
381    *
382    * @param event the event object containing the removed component descriptor
383    * @param componentManager the {@link ComponentManager} where the descriptor is registered
384    * @param descriptor the component descriptor removed from the component manager
385    */
 
386  21 toggle private void onEventListenerComponentRemoved(ComponentDescriptorRemovedEvent event,
387    ComponentManager componentManager, ComponentDescriptor<?> descriptor)
388    {
389  21 EventListener removedEventListener = null;
390  21 for (EventListener eventListener : getListenersByName().values()) {
391  578 if (eventListener.getClass() == descriptor.getImplementation()) {
392  16 removedEventListener = eventListener;
393    }
394    }
395   
396  21 if (removedEventListener != null) {
397  16 removeListener(removedEventListener.getName());
398    }
399    }
400    }