1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.observation.internal

File DefaultObservationManager.java

 

Coverage histogram

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

Code metrics

42
86
17
2
395
234
41
0.48
5.06
8.5
2.41

Classes

Class Line # Actions
DefaultObservationManager 55 82 0% 38 7
0.949275494.9%
DefaultObservationManager.RegisteredListener 83 4 0% 3 0
1.0100%
 

Contributing tests

This file is covered by 282 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: 2910f53af429654d7167fb585f0055f1e7eb1e3a $
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  17677 toggle RegisteredListener(EventListener listener, Event event)
101    {
102  17677 addEvent(event);
103   
104  17677 this.listener = listener;
105    }
106   
107    /**
108    * @param event the event to add
109    */
 
110  17830 toggle void addEvent(Event event)
111    {
112  17830 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  494179 toggle private Map<Class<? extends Event>, Map<String, RegisteredListener>> getListenersByEvent()
129    {
130  494152 if (this.listenersByEvent == null) {
131  240 initializeListeners();
132    }
133   
134  494143 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  9352 toggle private Map<String, EventListener> getListenersByName()
142    {
143  9352 if (this.listenersByName == null) {
144  115 initializeListeners();
145    }
146   
147  9352 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  355 toggle private synchronized void initializeListeners()
156    {
157  355 if (this.listenersByName == null) {
158  355 this.listenersByEvent = new ConcurrentHashMap<>();
159  355 this.listenersByName = new ConcurrentHashMap<>();
160   
161    // Can be null in unit tests
162  355 if (this.componentManager != null) {
163  355 try {
164  355 for (EventListener listener : this.componentManager
165    .<EventListener>getInstanceList(EventListener.class)) {
166  5204 addListener(listener);
167    }
168    } catch (ComponentLookupException e) {
169  1 this.logger.error("Failed to lookup listeners", e);
170    }
171    }
172    }
173    }
174   
 
175  7760 toggle @Override
176    public void addListener(EventListener eventListener)
177    {
178  7760 Map<String, EventListener> listeners = getListenersByName();
179   
180    // Remove previous listener if any
181  7760 EventListener previousListener = listeners.get(eventListener.getName());
182  7760 if (previousListener != null) {
183  36 removeListener(eventListener.getName());
184   
185  36 this.logger.warn(
186    "The [{}] listener is overwritting 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  7760 listeners.put(eventListener.getName(), eventListener);
195   
196    // For each event defined for this listener, add it to the Event Map.
197  7760 for (Event event : eventListener.getEvents()) {
198    // Check if this is a new Event type not already registered
199  17827 Map<String, RegisteredListener> eventListeners = this.listenersByEvent.get(event.getClass());
200  17827 if (eventListeners == null) {
201    // No listener registered for this event yet. Create a map to store listeners for this event.
202  8530 eventListeners = new ConcurrentHashMap<String, RegisteredListener>();
203  8530 this.listenersByEvent.put(event.getClass(), eventListeners);
204    // There is no RegisteredListener yet, create one
205  8530 eventListeners.put(eventListener.getName(), new RegisteredListener(eventListener, event));
206    } else {
207    // Add an event to existing RegisteredListener object
208  9297 RegisteredListener registeredListener = eventListeners.get(eventListener.getName());
209  9297 if (registeredListener == null) {
210  9146 eventListeners.put(eventListener.getName(), new RegisteredListener(eventListener, event));
211    } else {
212  151 registeredListener.addEvent(event);
213    }
214    }
215    }
216    }
217   
 
218  1534 toggle @Override
219    public void removeListener(String listenerName)
220    {
221  1534 getListenersByName().remove(listenerName);
222  1534 for (Map.Entry<Class<? extends Event>, Map<String, RegisteredListener>> entry : this.listenersByEvent
223    .entrySet()) {
224  42996 entry.getValue().remove(listenerName);
225  42996 if (entry.getValue().isEmpty()) {
226  2800 this.listenersByEvent.remove(entry.getKey());
227    }
228    }
229    }
230   
 
231  3 toggle @Override
232    public void addEvent(String listenerName, Event event)
233    {
234  3 Map<String, RegisteredListener> listeners = getListenersByEvent().get(event.getClass());
235  3 if (listeners == null) {
236  1 listeners = new ConcurrentHashMap<>();
237  1 this.listenersByEvent.put(event.getClass(), listeners);
238    }
239  3 RegisteredListener listener = listeners.get(listenerName);
240  3 if (listener != null) {
241  2 listener.addEvent(event);
242    } else {
243  1 listeners.put(listenerName, new RegisteredListener(this.getListener(listenerName), event));
244    }
245    }
246   
 
247  1 toggle @Override
248    public void removeEvent(String listenerName, Event event)
249    {
250  1 Map<String, RegisteredListener> listeners = getListenersByEvent().get(event.getClass());
251  1 RegisteredListener listener = listeners.get(listenerName);
252  1 if (listener != null) {
253  1 listener.removeEvent(event);
254    }
255    }
256   
 
257  40 toggle @Override
258    public EventListener getListener(String listenerName)
259    {
260  40 return getListenersByName().get(listenerName);
261    }
262   
 
263  494172 toggle @Override
264    public void notify(Event event, Object source, Object data)
265    {
266    // Find all listeners for this event
267  494162 Map<String, RegisteredListener> regListeners = getListenersByEvent().get(event.getClass());
268  494152 if (regListeners != null) {
269  82209 notify(regListeners.values(), event, source, data);
270    }
271   
272    // Find listener listening all events
273  494160 Map<String, RegisteredListener> allEventRegListeners = this.listenersByEvent.get(AllEvent.class);
274  494155 if (allEventRegListeners != null) {
275  494035 notify(allEventRegListeners.values(), event, source, data);
276    }
277   
278    // We want this Observation Manager to be able to handle new Event Listener components being added or removed
279    // at runtime. Thus ideally we should make this Manager an Event Listener itself. However in order to avoid
280    // circular dependencies issues and in order to be more performant we simply handle ComponentDescriptorEvents
281    // here to add/remove Event Listeners.
282  494197 if (event instanceof ComponentDescriptorEvent) {
283  1110 onComponentEvent((ComponentDescriptorEvent) event, (ComponentManager) source,
284    (ComponentDescriptor<EventListener>) data);
285    }
286    }
287   
288    /**
289    * Call the provided listeners matching the passed Event. The definition of <em>source</em> and <em>data</em> is
290    * purely up to the communicating classes.
291    *
292    * @param listeners the listeners to notify
293    * @param event the event to pass to the registered listeners
294    * @param source the source of the event (or <code>null</code>)
295    * @param data the additional data related to the event (or <code>null</code>)
296    */
 
297  576243 toggle private void notify(Collection<RegisteredListener> listeners, Event event, Object source, Object data)
298    {
299  576238 for (RegisteredListener listener : listeners) {
300    // Verify that one of the events matches and send the first matching event
301  697951 for (Event listenerEvent : listener.events) {
302  715967 if (listenerEvent.matches(event)) {
303  626028 try {
304  626026 listener.listener.onEvent(event, source, data);
305    } catch (Exception e) {
306    // protect from bad listeners
307  0 this.logger.error("Failed to send event [{}] to listener [{}]", new Object[] { event,
308    listener.listener, e });
309    }
310   
311    // Only send the first matching event since the listener should only be called once per event.
312  625977 break;
313    }
314    }
315    }
316    }
317   
 
318  188573 toggle @Override
319    public void notify(Event event, Object source)
320    {
321  188566 notify(event, source, null);
322    }
323   
324    /**
325    * A Component has been modified (added or removed) and we update our cache of Event Listeners if that Component is
326    * an Event Listener.
327    *
328    * @param componentEvent the event about the Component being added or removed
329    * @param componentManager the {@link ComponentManager} where the descriptor is registered
330    * @param descriptor the descriptor of the modified component
331    */
 
332  1110 toggle private void onComponentEvent(ComponentDescriptorEvent componentEvent, ComponentManager componentManager,
333    ComponentDescriptor<EventListener> descriptor)
334    {
335  1110 if (componentEvent.getRoleType() == EventListener.class) {
336  49 if (componentEvent instanceof ComponentDescriptorAddedEvent) {
337  31 onEventListenerComponentAdded((ComponentDescriptorAddedEvent) componentEvent, componentManager,
338    descriptor);
339  18 } else if (componentEvent instanceof ComponentDescriptorRemovedEvent) {
340  18 onEventListenerComponentRemoved((ComponentDescriptorRemovedEvent) componentEvent, componentManager,
341    descriptor);
342    } else {
343  0 this.logger.warn("Ignoring unknown Component event [{}]", componentEvent.getClass().getName());
344    }
345    }
346    }
347   
348    /**
349    * An Event Listener Component has been dynamically registered in the system, add it to our cache.
350    *
351    * @param event event object containing the new component descriptor
352    * @param componentManager the {@link ComponentManager} where the descriptor is registered
353    * @param descriptor the component descriptor removed from component manager
354    */
 
355  31 toggle private void onEventListenerComponentAdded(ComponentDescriptorAddedEvent event, ComponentManager componentManager,
356    ComponentDescriptor<EventListener> descriptor)
357    {
358  31 try {
359  31 EventListener eventListener = componentManager.getInstance(EventListener.class, event.getRoleHint());
360   
361  31 if (getListener(eventListener.getName()) != eventListener) {
362  29 addListener(eventListener);
363    } else {
364  2 this.logger.warn("An Event Listener named [{}] already exists, ignoring the [{}] component",
365    eventListener.getName(), descriptor.getImplementation().getName());
366    }
367    } catch (ComponentLookupException e) {
368  0 this.logger.error("Failed to lookup the Event Listener [{}] corresponding to the Component registration "
369    + "event for [{}]. Ignoring the event", new Object[] { event.getRoleHint(),
370    descriptor.getImplementation().getName(), e });
371    }
372    }
373   
374    /**
375    * An Event Listener Component has been dynamically unregistered in the system, remove it from our cache.
376    *
377    * @param event the event object containing the removed component descriptor
378    * @param componentManager the {@link ComponentManager} where the descriptor is registered
379    * @param descriptor the component descriptor removed from the component manager
380    */
 
381  18 toggle private void onEventListenerComponentRemoved(ComponentDescriptorRemovedEvent event,
382    ComponentManager componentManager, ComponentDescriptor<?> descriptor)
383    {
384  18 EventListener removedEventListener = null;
385  18 for (EventListener eventListener : getListenersByName().values()) {
386  153 if (eventListener.getClass() == descriptor.getImplementation()) {
387  13 removedEventListener = eventListener;
388    }
389    }
390   
391  18 if (removedEventListener != null) {
392  13 removeListener(removedEventListener.getName());
393    }
394    }
395    }