View Javadoc

1   /***
2    *  Copyright 2003-2010 Terracotta, Inc.
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  
17  package net.sf.ehcache.event;
18  
19  import net.sf.ehcache.CacheException;
20  import net.sf.ehcache.Ehcache;
21  import net.sf.ehcache.Element;
22  import net.sf.ehcache.distribution.CacheReplicator;
23  
24  import java.util.HashSet;
25  import java.util.Iterator;
26  import java.util.Set;
27  import java.util.concurrent.CopyOnWriteArraySet;
28  import java.util.concurrent.atomic.AtomicLong;
29  
30  /***
31   * Registered listeners for registering and unregistering CacheEventListeners and multicasting notifications to registrants.
32   * <p/>
33   * There is one of these per Cache.
34   * <p/>
35   * This class also has counters to accumulate the numbers of each type of event for statistics purposes.
36   *
37   * @author Greg Luck
38   * @author Geert Bevin
39   * @version $Id: RegisteredEventListeners.html 13146 2011-08-01 17:12:39Z oletizi $
40   */
41  public class RegisteredEventListeners {
42  
43      /***
44       * A Set of CacheEventListeners keyed by listener instance.
45       * CacheEventListener implementations that will be notified of this cache's events.
46       *
47       * @see CacheEventListener
48       */
49      private final Set<ListenerWrapper> cacheEventListeners = new CopyOnWriteArraySet<ListenerWrapper>();
50      private final Ehcache cache;
51  
52      private AtomicLong elementsRemovedCounter = new AtomicLong(0);
53      private AtomicLong elementsPutCounter = new AtomicLong(0);
54      private AtomicLong elementsUpdatedCounter = new AtomicLong(0);
55      private AtomicLong elementsExpiredCounter = new AtomicLong(0);
56      private AtomicLong elementsEvictedCounter = new AtomicLong(0);
57      private AtomicLong elementsRemoveAllCounter = new AtomicLong(0);
58  
59      /***
60       * Constructs a new notification service
61       *
62       * @param cache
63       */
64      public RegisteredEventListeners(Ehcache cache) {
65          this.cache = cache;
66      }
67  
68      /***
69       * Notifies all registered listeners, in no guaranteed order, that an element was removed
70       *
71       * @param element
72       * @param remoteEvent whether the event came from a remote cache peer
73       * @see CacheEventListener#notifyElementRemoved
74       */
75      public final void notifyElementRemoved(Element element, boolean remoteEvent) throws CacheException {
76          elementsRemovedCounter.incrementAndGet();
77          if (hasCacheEventListeners()) {
78              for (ListenerWrapper listenerWrapper : cacheEventListeners) {
79                  if (listenerWrapper.getScope().shouldDeliver(remoteEvent)
80                          && !isCircularNotification(remoteEvent, listenerWrapper.getListener())) {
81                      listenerWrapper.getListener().notifyElementRemoved(cache, element);
82                  }
83              }
84          }
85      }
86  
87      /***
88       * Notifies all registered listeners, in no guaranteed order, that an element was put into the cache
89       *
90       * @param element
91       * @param remoteEvent whether the event came from a remote cache peer
92       * @see CacheEventListener#notifyElementPut(net.sf.ehcache.Ehcache,net.sf.ehcache.Element)
93       */
94      public final void notifyElementPut(Element element, boolean remoteEvent) throws CacheException {
95          elementsPutCounter.incrementAndGet();
96          if (hasCacheEventListeners()) {
97              for (ListenerWrapper listenerWrapper : cacheEventListeners) {
98                  if (listenerWrapper.getScope().shouldDeliver(remoteEvent)
99                          && !isCircularNotification(remoteEvent, listenerWrapper.getListener())) {
100                     listenerWrapper.getListener().notifyElementPut(cache, element);
101                 }
102             }
103 
104         }
105     }
106 
107     /***
108      * Notifies all registered listeners, in no guaranteed order, that an element in the cache was updated
109      *
110      * @param element
111      * @param remoteEvent whether the event came from a remote cache peer
112      * @see CacheEventListener#notifyElementPut(net.sf.ehcache.Ehcache,net.sf.ehcache.Element)
113      */
114     public final void notifyElementUpdated(Element element, boolean remoteEvent) {
115         elementsUpdatedCounter.incrementAndGet();
116         if (hasCacheEventListeners()) {
117             for (ListenerWrapper listenerWrapper : cacheEventListeners) {
118                 if (listenerWrapper.getScope().shouldDeliver(remoteEvent)
119                         && !isCircularNotification(remoteEvent, listenerWrapper.getListener())) {
120                     listenerWrapper.getListener().notifyElementUpdated(cache, element);
121                 }
122             }
123         }
124     }
125 
126     /***
127      * Notifies all registered listeners, in no guaranteed order, that an element has expired
128      *
129      * @param element     the Element to perform the notification on
130      * @param remoteEvent whether the event came from a remote cache peer
131      * @see CacheEventListener#notifyElementExpired
132      */
133     public final void notifyElementExpiry(Element element, boolean remoteEvent) {
134         elementsExpiredCounter.incrementAndGet();
135         if (hasCacheEventListeners()) {
136             for (ListenerWrapper listenerWrapper : cacheEventListeners) {
137                 if (listenerWrapper.getScope().shouldDeliver(remoteEvent)
138                         && !isCircularNotification(remoteEvent, listenerWrapper.getListener())) {
139                     listenerWrapper.getListener().notifyElementExpired(cache, element);
140                 }
141             }
142         }
143     }
144 
145     /***
146      * Returns whether or not at least one cache event listeners has been registered.
147      *
148      * @return true if a one or more listeners have registered, otherwise false
149      */
150     public final boolean hasCacheEventListeners() {
151         return cacheEventListeners.size() > 0;
152     }
153 
154     /***
155      * Notifies all registered listeners, in no guaranteed order, that an element has been
156      * evicted from the cache
157      *
158      * @param element     the Element to perform the notification on
159      * @param remoteEvent whether the event came from a remote cache peer
160      * @see CacheEventListener#notifyElementEvicted
161      */
162     public void notifyElementEvicted(Element element, boolean remoteEvent) {
163         elementsEvictedCounter.incrementAndGet();
164         if (hasCacheEventListeners()) {
165             for (ListenerWrapper listenerWrapper : cacheEventListeners) {
166                 if (listenerWrapper.getScope().shouldDeliver(remoteEvent)
167                         && !isCircularNotification(remoteEvent, listenerWrapper.getListener())) {
168                     listenerWrapper.getListener().notifyElementEvicted(cache, element);
169                 }
170             }
171         }
172     }
173 
174     /***
175      * Notifies all registered listeners, in no guaranteed order, that removeAll
176      * has been called and all elements cleared
177      *
178      * @param remoteEvent whether the event came from a remote cache peer
179      * @see CacheEventListener#notifyElementEvicted
180      */
181     public void notifyRemoveAll(boolean remoteEvent) {
182         elementsRemoveAllCounter.incrementAndGet();
183         if (hasCacheEventListeners()) {
184             for (ListenerWrapper listenerWrapper : cacheEventListeners) {
185                 if (listenerWrapper.getScope().shouldDeliver(remoteEvent)
186                         && !isCircularNotification(remoteEvent, listenerWrapper.getListener())) {
187                     listenerWrapper.getListener().notifyRemoveAll(cache);
188                 }
189             }
190         }
191     }
192 
193     /***
194      * CacheReplicators should not be notified of events received remotely, as this would cause
195      * a circular notification
196      *
197      * @param remoteEvent
198      * @param cacheEventListener
199      * @return true is notifiying the listener would cause a circular notification
200      */
201     private static boolean isCircularNotification(boolean remoteEvent, CacheEventListener cacheEventListener) {
202         return remoteEvent && cacheEventListener instanceof CacheReplicator;
203     }
204 
205 
206     /***
207      * Adds a listener to the notification service. No guarantee is made that listeners will be
208      * notified in the order they were added.
209      *
210      * @param cacheEventListener
211      * @return true if the listener is being added and was not already added
212      */
213     public final boolean registerListener(CacheEventListener cacheEventListener) {
214         return registerListener(cacheEventListener, NotificationScope.ALL);
215     }
216 
217     /***
218      * Adds a listener to the notification service. No guarantee is made that listeners will be
219      * notified in the order they were added.
220      * <p/>
221      * If a cache is configured in a cluster, listeners in each node will get triggered by an event
222      * depending on the value of the <pre>listenFor</pre> parameter.
223      *
224      * @param cacheEventListener The listener to add
225      * @param scope              The notification scope
226      * @return true if the listener is being added and was not already added
227      * @since 2.0
228      */
229     public final boolean registerListener(CacheEventListener cacheEventListener, NotificationScope scope) {
230         if (cacheEventListener == null) {
231             return false;
232         }
233         return cacheEventListeners.add(new ListenerWrapper(cacheEventListener, scope));
234     }
235 
236     /***
237      * Removes a listener from the notification service.
238      *
239      * @param cacheEventListener
240      * @return true if the listener was present
241      */
242     public final boolean unregisterListener(CacheEventListener cacheEventListener) {
243         Iterator<ListenerWrapper> it = cacheEventListeners.iterator();
244         while (it.hasNext()) {
245             ListenerWrapper listenerWrapper = it.next();
246             if (listenerWrapper.getListener().equals(cacheEventListener)) {
247                 cacheEventListeners.remove(listenerWrapper);
248                 return true;
249             }
250         }
251         return false;
252     }
253 
254 
255     /***
256      * Gets a copy of the set of the listeners registered to this class
257      *
258      * @return a set of type <code>CacheEventListener</code>
259      */
260     public final Set<CacheEventListener> getCacheEventListeners() {
261         Set<CacheEventListener> listenerSet = new HashSet<CacheEventListener>();
262         for (ListenerWrapper listenerWrapper : cacheEventListeners) {
263             listenerSet.add(listenerWrapper.getListener());
264         }
265         return listenerSet;
266     }
267 
268     /***
269      * Tell listeners to dispose themselves.
270      * Because this method is only ever called from a synchronized cache method, it does not itself need to be
271      * synchronized.
272      */
273     public final void dispose() {
274         for (ListenerWrapper listenerWrapper : cacheEventListeners) {
275             listenerWrapper.getListener().dispose();
276         }
277         cacheEventListeners.clear();
278     }
279 
280     /***
281      * Returns a string representation of the object. In general, the
282      * <code>toString</code> method returns a string that
283      * "textually represents" this object. The result should
284      * be a concise but informative representation that is easy for a
285      * person to read.
286      *
287      * @return a string representation of the object.
288      */
289     @Override
290     public final String toString() {
291         StringBuilder sb = new StringBuilder(" cacheEventListeners: ");
292         for (ListenerWrapper listenerWrapper : cacheEventListeners) {
293             sb.append(listenerWrapper.getListener().getClass().getName()).append(" ");
294         }
295         return sb.toString();
296     }
297 
298     /***
299      * Clears all event counters
300      */
301     public void clearCounters() {
302         elementsRemovedCounter.set(0);
303         elementsPutCounter.set(0);
304         elementsUpdatedCounter.set(0);
305         elementsExpiredCounter.set(0);
306         elementsEvictedCounter.set(0);
307         elementsRemoveAllCounter.set(0);
308     }
309 
310     /***
311      * Gets the number of events, irrespective of whether there are any registered listeners.
312      *
313      * @return the number of events since cache creation or last clearing of counters
314      */
315     public long getElementsRemovedCounter() {
316         return elementsRemovedCounter.get();
317     }
318 
319     /***
320      * Gets the number of events, irrespective of whether there are any registered listeners.
321      *
322      * @return the number of events since cache creation or last clearing of counters
323      */
324     public long getElementsPutCounter() {
325         return elementsPutCounter.get();
326     }
327 
328     /***
329      * Gets the number of events, irrespective of whether there are any registered listeners.
330      *
331      * @return the number of events since cache creation or last clearing of counters
332      */
333     public long getElementsUpdatedCounter() {
334         return elementsUpdatedCounter.get();
335     }
336 
337     /***
338      * Gets the number of events, irrespective of whether there are any registered listeners.
339      *
340      * @return the number of events since cache creation or last clearing of counters
341      */
342     public long getElementsExpiredCounter() {
343         return elementsExpiredCounter.get();
344     }
345 
346     /***
347      * Gets the number of events, irrespective of whether there are any registered listeners.
348      *
349      * @return the number of events since cache creation or last clearing of counters
350      */
351     public long getElementsEvictedCounter() {
352         return elementsEvictedCounter.get();
353     }
354 
355     /***
356      * Gets the number of events, irrespective of whether there are any registered listeners.
357      *
358      * @return the number of events since cache creation or last clearing of counters
359      */
360     public long getElementsRemoveAllCounter() {
361         return elementsRemoveAllCounter.get();
362     }
363 
364     /***
365      * Combine a Listener and its NotificationScope.  Equality and hashcode are based purely on the listener.
366      * This implies that the same listener cannot be added to the set of registered listeners more than
367      * once with different notification scopes.
368      *
369      * @author Alex Miller
370      */
371     private static final class ListenerWrapper {
372         private final CacheEventListener listener;
373         private final NotificationScope scope;
374 
375         private ListenerWrapper(CacheEventListener listener, NotificationScope scope) {
376             this.listener = listener;
377             this.scope = scope;
378         }
379 
380         private CacheEventListener getListener() {
381             return this.listener;
382         }
383 
384         private NotificationScope getScope() {
385             return this.scope;
386         }
387 
388         /***
389          * Hash code based on listener
390          *
391          * @see java.lang.Object#hashCode()
392          */
393         @Override
394         public int hashCode() {
395             return listener.hashCode();
396         }
397 
398         /***
399          * Equals based on listener (NOT based on scope) - can't have same listener with two different scopes
400          *
401          * @see java.lang.Object#equals(java.lang.Object)
402          */
403         @Override
404         public boolean equals(Object obj) {
405             if (this == obj) {
406                 return true;
407             }
408             if (obj == null) {
409                 return false;
410             }
411             if (getClass() != obj.getClass()) {
412                 return false;
413             }
414             ListenerWrapper other = (ListenerWrapper) obj;
415             if (listener == null) {
416                 if (other.listener != null) {
417                     return false;
418                 }
419             } else if (!listener.equals(other.listener)) {
420                 return false;
421             }
422             return true;
423         }
424 
425         @Override
426         public String toString() {
427             return listener.toString();
428         }
429     }
430 
431 }