Clover Coverage Report - XWiki Commons - Parent POM 4.0-SNAPSHOT (Aggregated)
Coverage timestamp: Mon Mar 12 2012 17:13:48 CET
../../../../img/srcFileCovDistChart10.png 0% of files have more coverage
69   352   34   4.6
32   208   0.49   7.5
15     2.27  
2    
 
  DefaultObservationManager       Line # 57 65 0% 31 9 91.7% 0.9174312
  DefaultObservationManager.RegisteredListener       Line # 90 4 0% 3 0 100% 1.0
 
No Tests
 
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.component.phase.Initializable;
40    import org.xwiki.component.phase.InitializationException;
41    import org.xwiki.observation.EventListener;
42    import org.xwiki.observation.ObservationManager;
43    import org.xwiki.observation.event.AllEvent;
44    import org.xwiki.observation.event.Event;
45   
46    /**
47    * Default implementation of the {@link ObservationManager}.
48    * <p>
49    * This component use synchronized for concurrent protection instead of having
50    * {@link java.util.concurrent.ConcurrentHashMap} everywhere because it's more efficient since most of methods access to
51    * several maps and generally do enumerations.
52    *
53    * @version $Id: e328ae4e0be246560f10ba4d92853805e44869bc $
54    */
55    @Component
56    @Singleton
 
57    public class DefaultObservationManager implements ObservationManager, Initializable
58    {
59    /**
60    * Registered listeners indexed on Event classes so that it's fast to find all the listeners registered for a given
61    * event, so that {@link #notify} calls execute fast and in a fixed amount a time.
62    *
63    * @todo Should we allow event inheritance?
64    */
65    private Map<Class< ? extends Event>, Map<String, RegisteredListener>> listenersByEvent =
66    new ConcurrentHashMap<Class< ? extends Event>, Map<String, RegisteredListener>>();
67   
68    /**
69    * Registered listeners index by listener name. It makes it fast to perform operations on already registered
70    * listeners.
71    */
72    private Map<String, EventListener> listenersByName = new ConcurrentHashMap<String, EventListener>();
73   
74    /**
75    * Used to find all components implementing {@link EventListener} to register them automatically.
76    */
77    @Inject
78    private ComponentManager componentManager;
79   
80    /**
81    * The logger to log.
82    */
83    @Inject
84    private Logger logger;
85   
86    /**
87    * Helper class to store the list of events of a given type associated with a given listener. We need this for
88    * performance reasons and also in order to be able to add events after a listener has been registered.
89    */
 
90    private static class RegisteredListener
91    {
92    /**
93    * Events of a given type associated with a given listener.
94    */
95    private List<Event> events = new ArrayList<Event>();
96   
97    /**
98    * Listener associated with the events.
99    */
100    private EventListener listener;
101   
102    /**
103    * @param listener the listener associated with the events.
104    * @param event the first event to associate with the passed listener. More events are added by calling
105    * {@link #addEvent(Event)}
106    */
 
107  733 toggle RegisteredListener(EventListener listener, Event event)
108    {
109  733 addEvent(event);
110   
111  733 this.listener = listener;
112    }
113   
114    /**
115    * @param event the event to add
116    */
 
117  737 toggle void addEvent(Event event)
118    {
119  737 this.events.add(event);
120    }
121   
122    /**
123    * @param event the event to remove
124    */
 
125  1 toggle void removeEvent(Event event)
126    {
127  1 this.events.remove(event);
128    }
129    }
130   
 
131  34 toggle @Override
132    public void initialize() throws InitializationException
133    {
134  34 try {
135  34 for (EventListener listener : this.componentManager.lookupList(EventListener.class)) {
136  106 addListener(listener);
137    }
138    } catch (ComponentLookupException e) {
139  0 throw new InitializationException("Failed to lookup Event Listeners", e);
140    }
141    }
142   
 
143  490 toggle @Override
144    public void addListener(EventListener eventListener)
145    {
146    // Register the listener by name. If already registered, override it.
147  490 EventListener previousListener = this.listenersByName.put(eventListener.getName(), eventListener);
148   
149    // If the passed event listener name is already registered, log a warning
150  490 if (previousListener != null) {
151  11 this.logger.warn(
152    "The [{}] listener has overwritten a previously "
153    + "registered listener [{}] since they both are registered under the same id [{}]. "
154    + "In the future consider removing a Listener first if you really want to register it again.",
155    new Object[] {eventListener.getClass().getName(), previousListener.getClass().getName(),
156    eventListener.getName()});
157    }
158   
159    // For each event defined for this listener, add it to the Event Map.
160  490 for (Event event : eventListener.getEvents()) {
161    // Check if this is a new Event type not already registered
162  735 Map<String, RegisteredListener> eventListeners = this.listenersByEvent.get(event.getClass());
163  735 if (eventListeners == null) {
164    // No listener registered for this event yet. Create a map to store listeners for this event.
165  534 eventListeners = new ConcurrentHashMap<String, RegisteredListener>();
166  534 this.listenersByEvent.put(event.getClass(), eventListeners);
167    // There is no RegisteredListener yet, create one
168  534 eventListeners.put(eventListener.getName(), new RegisteredListener(eventListener, event));
169    } else {
170    // Add an event to existing RegisteredListener object
171  201 RegisteredListener registeredListener = eventListeners.get(eventListener.getName());
172  201 if (registeredListener == null) {
173  199 eventListeners.put(eventListener.getName(), new RegisteredListener(eventListener, event));
174    } else {
175  2 registeredListener.addEvent(event);
176    }
177    }
178    }
179    }
180   
 
181  353 toggle @Override
182    public void removeListener(String listenerName)
183    {
184  353 this.listenersByName.remove(listenerName);
185  353 for (Map.Entry<Class< ? extends Event>, Map<String, RegisteredListener>> entry : this.listenersByEvent
186    .entrySet()) {
187  2762 entry.getValue().remove(listenerName);
188  2762 if (entry.getValue().isEmpty()) {
189  419 this.listenersByEvent.remove(entry.getKey());
190    }
191    }
192    }
193   
 
194  2 toggle @Override
195    public void addEvent(String listenerName, Event event)
196    {
197  2 Map<String, RegisteredListener> listeners = this.listenersByEvent.get(event.getClass());
198  2 RegisteredListener listener = listeners.get(listenerName);
199  2 if (listener != null) {
200  2 listener.addEvent(event);
201    }
202    }
203   
 
204  1 toggle @Override
205    public void removeEvent(String listenerName, Event event)
206    {
207  1 Map<String, RegisteredListener> listeners = this.listenersByEvent.get(event.getClass());
208  1 RegisteredListener listener = listeners.get(listenerName);
209  1 if (listener != null) {
210  1 listener.removeEvent(event);
211    }
212    }
213   
 
214  33 toggle @Override
215    public EventListener getListener(String listenerName)
216    {
217  33 return this.listenersByName.get(listenerName);
218    }
219   
 
220  2262 toggle @Override
221    public void notify(Event event, Object source, Object data)
222    {
223    // Find all listeners for this event
224  2262 Map<String, RegisteredListener> regListeners = this.listenersByEvent.get(event.getClass());
225  2262 if (regListeners != null) {
226  1950 notify(regListeners.values(), event, source, data);
227    }
228   
229    // Find listener listening all events
230  2262 Map<String, RegisteredListener> allEventRegListeners = this.listenersByEvent.get(AllEvent.class);
231  2262 if (allEventRegListeners != null) {
232  2255 notify(allEventRegListeners.values(), event, source, data);
233    }
234   
235    // We want this Observation Manager to be able to handle new Event Listener components being added or removed
236    // at runtime. Thus ideally we should make this Manager an Event Listener itself. However in order to avoid
237    // circular dependencies issues and in order to be more performant we simply handle ComponentDescriptorEvents
238    // here to add/remove Event Listeners.
239  2262 if (event instanceof ComponentDescriptorEvent) {
240  123 onComponentEvent((ComponentDescriptorEvent) event, (ComponentManager) source,
241    (ComponentDescriptor<EventListener>) data);
242    }
243    }
244   
245    /**
246    * Call the provided listeners matching the passed Event. The definition of <em>source</em> and <em>data</em> is
247    * purely up to the communicating classes.
248    *
249    * @param listeners the listeners to notify
250    * @param event the event to pass to the registered listeners
251    * @param source the source of the event (or <code>null</code>)
252    * @param data the additional data related to the event (or <code>null</code>)
253    */
 
254  4205 toggle private void notify(Collection<RegisteredListener> listeners, Event event, Object source, Object data)
255    {
256  4205 for (RegisteredListener listener : listeners) {
257    // Verify that one of the events matches and send the first matching event
258  4943 for (Event listenerEvent : listener.events) {
259  4945 if (listenerEvent.matches(event)) {
260  4942 try {
261  4942 listener.listener.onEvent(event, source, data);
262    } catch (Exception e) {
263    // protect from bad listeners
264  0 this.logger.error("Failed to send event [{}] to listener [{}]", new Object[] {event,
265    listener.listener, e});
266    }
267   
268    // Only send the first matching event since the listener should only be called once per event.
269  4942 break;
270    }
271    }
272    }
273    }
274   
 
275  1954 toggle @Override
276    public void notify(Event event, Object source)
277    {
278  1954 notify(event, source, null);
279    }
280   
281    /**
282    * A Component has been modified (added or removed) and we update our cache of Event Listeners if that Component is
283    * an Event Listener.
284    *
285    * @param componentEvent the event about the Component being added or removed
286    * @param componentManager the {@link ComponentManager} where the descriptor is registered
287    * @param descriptor the descriptor of the modified component
288    */
 
289  123 toggle private void onComponentEvent(ComponentDescriptorEvent componentEvent, ComponentManager componentManager,
290    ComponentDescriptor<EventListener> descriptor)
291    {
292  123 if (componentEvent.getRole() == EventListener.class) {
293  46 if (componentEvent instanceof ComponentDescriptorAddedEvent) {
294  29 onEventListenerComponentAdded((ComponentDescriptorAddedEvent) componentEvent, componentManager,
295    descriptor);
296  17 } else if (componentEvent instanceof ComponentDescriptorRemovedEvent) {
297  17 onEventListenerComponentRemoved((ComponentDescriptorRemovedEvent) componentEvent, componentManager,
298    descriptor);
299    } else {
300  0 this.logger.warn("Ignoring unknown Component event [{}]", componentEvent.getClass().getName());
301    }
302    }
303    }
304   
305    /**
306    * An Event Listener Component has been dynamically registered in the system, add it to our cache.
307    *
308    * @param event event object containing the new component descriptor
309    * @param componentManager the {@link ComponentManager} where the descriptor is registered
310    * @param descriptor the component descriptor removed from component manager
311    */
 
312  29 toggle private void onEventListenerComponentAdded(ComponentDescriptorAddedEvent event, ComponentManager componentManager,
313    ComponentDescriptor<EventListener> descriptor)
314    {
315  29 try {
316  29 EventListener eventListener = componentManager.lookup(EventListener.class, event.getRoleHint());
317   
318  29 if (getListener(eventListener.getName()) != eventListener) {
319  29 addListener(eventListener);
320    } else {
321  0 this.logger.warn("An Event Listener named [{}] already exists, ignoring the [{}] component",
322    eventListener.getName(), descriptor.getImplementation().getName());
323    }
324    } catch (ComponentLookupException e) {
325  0 this.logger.error("Failed to lookup the Event Listener [{}] corresponding to the Component registration "
326    + "event for [{}]. Ignoring the event", new Object[] {event.getRoleHint(),
327    descriptor.getImplementation().getName(), e});
328    }
329    }
330   
331    /**
332    * An Event Listener Component has been dynamically unregistered in the system, remove it from our cache.
333    *
334    * @param event the event object containing the removed component descriptor
335    * @param componentManager the {@link ComponentManager} where the descriptor is registered
336    * @param descriptor the component descriptor removed from the component manager
337    */
 
338  17 toggle private void onEventListenerComponentRemoved(ComponentDescriptorRemovedEvent event,
339    ComponentManager componentManager, ComponentDescriptor< ? > descriptor)
340    {
341  17 EventListener removedEventListener = null;
342  17 for (EventListener eventListener : this.listenersByName.values()) {
343  101 if (eventListener.getClass() == descriptor.getImplementation()) {
344  9 removedEventListener = eventListener;
345    }
346    }
347   
348  17 if (removedEventListener != null) {
349  9 removeListener(removedEventListener.getName());
350    }
351    }
352    }