1 /**
2  *  Copyright 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.Cache;
20 import net.sf.ehcache.CacheException;
21 import net.sf.ehcache.CacheStoreHelper;
22 import net.sf.ehcache.Ehcache;
23 import net.sf.ehcache.Element;
24 import net.sf.ehcache.distribution.CacheReplicator;
25 import net.sf.ehcache.statistics.LiveCacheStatisticsData;
26 import net.sf.ehcache.store.FrontEndCacheTier;
27 import net.sf.ehcache.store.Store;
28
29 import java.util.HashSet;
30 import java.util.Iterator;
31 import java.util.Set;
32 import java.util.concurrent.CopyOnWriteArraySet;
33 import java.util.concurrent.atomic.AtomicBoolean;
34 import java.util.concurrent.atomic.AtomicLong;
35
36 /**
37  * Registered listeners for registering and unregistering CacheEventListeners and multicasting notifications to registrants.
38  * <p/>
39  * There is one of these per Cache.
40  * <p/>
41  * This class also has counters to accumulate the numbers of each type of event for statistics purposes.
42  *
43  * @author Greg Luck
44  * @author Geert Bevin
45  * @version $Id: RegisteredEventListeners.java 5631 2012-05-10 08:31:33Z teck $
46  */

47 public class RegisteredEventListeners {
48
49     /**
50      * A Set of CacheEventListeners keyed by listener instance.
51      * CacheEventListener implementations that will be notified of this cache's events.
52      *
53      * @see CacheEventListener
54      */

55     private final Set<ListenerWrapper> cacheEventListeners = new CopyOnWriteArraySet<ListenerWrapper>();
56     private final Ehcache cache;
57
58     private final AtomicBoolean hasReplicator = new AtomicBoolean(false);
59
60     private final AtomicLong elementsRemovedCounter = new AtomicLong(0);
61     private final AtomicLong elementsPutCounter = new AtomicLong(0);
62     private final AtomicLong elementsUpdatedCounter = new AtomicLong(0);
63     private final AtomicLong elementsExpiredCounter = new AtomicLong(0);
64     private final AtomicLong elementsEvictedCounter = new AtomicLong(0);
65     private final AtomicLong elementsRemoveAllCounter = new AtomicLong(0);
66
67     private final CacheStoreHelper helper;
68
69     /**
70      * Constructs a new notification service
71      *
72      * @param cache
73      */

74     public RegisteredEventListeners(Ehcache cache) {
75         this.cache = cache;
76         helper = new CacheStoreHelper((Cache)cache);
77     }
78
79     /**
80      * Notifies all registered listeners, in no guaranteed order, that an element was removed
81      *
82      * @param element
83      * @param remoteEvent whether the event came from a remote cache peer
84      * @see CacheEventListener#notifyElementRemoved
85      */

86     public final void notifyElementRemoved(Element element, boolean remoteEvent) throws CacheException {
87         internalNotifyElementRemoved(element, null, remoteEvent);
88     }
89
90     /**
91      * Notifies all registered listeners, in no guaranteed order, that an element was removed
92      *
93      * @param callback
94      * @param remoteEvent whether the event came from a remote cache peer
95      * @see CacheEventListener#notifyElementRemoved
96      */

97     public final void notifyElementRemoved(ElementCreationCallback callback, boolean remoteEvent) throws CacheException {
98         internalNotifyElementRemoved(null, callback, remoteEvent);
99     }
100
101     private void internalNotifyElementRemoved(Element element, ElementCreationCallback callback, boolean remoteEvent) {
102         elementsRemovedCounter.incrementAndGet();
103         if (hasCacheEventListeners()) {
104             for (ListenerWrapper listenerWrapper : cacheEventListeners) {
105                 if (listenerWrapper.getScope().shouldDeliver(remoteEvent)
106                         && !isCircularNotification(remoteEvent, listenerWrapper.getListener())) {
107                     CacheEventListener listener = listenerWrapper.getListener();
108                     invokeListener(listener, element, callback, Event.REMOVED);
109                 }
110             }
111         }
112     }
113
114     /**
115      * Notifies all registered listeners, in no guaranteed order, that an element was put into the cache
116      *
117      * @param element
118      * @param remoteEvent whether the event came from a remote cache peer
119      * @see CacheEventListener#notifyElementPut(net.sf.ehcache.Ehcache,net.sf.ehcache.Element)
120      */

121     public final void notifyElementPut(Element element, boolean remoteEvent) throws CacheException {
122         internalNotifyElementPut(element, null, remoteEvent);
123     }
124
125     /**
126      * Notifies all registered listeners, in no guaranteed order, that an element was put into the cache
127      *
128      * @param callback
129      * @param remoteEvent whether the event came from a remote cache peer
130      * @see CacheEventListener#notifyElementPut(net.sf.ehcache.Ehcache,net.sf.ehcache.Element)
131      */

132
133     public final void notifyElementPut(ElementCreationCallback callback, boolean remoteEvent) throws CacheException {
134         internalNotifyElementPut(null, callback, remoteEvent);
135     }
136
137     private void internalNotifyElementPut(Element element, ElementCreationCallback callback, boolean remoteEvent) {
138         elementsPutCounter.incrementAndGet();
139         if (hasCacheEventListeners()) {
140             for (ListenerWrapper listenerWrapper : cacheEventListeners) {
141                 if (listenerWrapper.getScope().shouldDeliver(remoteEvent)
142                         && !isCircularNotification(remoteEvent, listenerWrapper.getListener())) {
143                     CacheEventListener listener = listenerWrapper.getListener();
144                     invokeListener(listener, element, callback, Event.PUT);
145                 }
146             }
147         }
148     }
149
150     /**
151      * Notifies all registered listeners, in no guaranteed order, that an element in the cache was updated
152      *
153      * @param element
154      * @param remoteEvent whether the event came from a remote cache peer
155      * @see CacheEventListener#notifyElementPut(net.sf.ehcache.Ehcache,net.sf.ehcache.Element)
156      */

157     public final void notifyElementUpdated(Element element, boolean remoteEvent) {
158         internalNotifyElementUpdated(element, null, remoteEvent);
159     }
160
161     /**
162      * Notifies all registered listeners, in no guaranteed order, that an element in the cache was updated
163      *
164      * @param callback
165      * @param remoteEvent whether the event came from a remote cache peer
166      * @see CacheEventListener#notifyElementPut(net.sf.ehcache.Ehcache,net.sf.ehcache.Element)
167      */

168
169     public final void notifyElementUpdated(ElementCreationCallback callback, boolean remoteEvent) {
170         internalNotifyElementUpdated(null, callback, remoteEvent);
171     }
172
173     private void internalNotifyElementUpdated(Element element, ElementCreationCallback callback, boolean remoteEvent) {
174         elementsUpdatedCounter.incrementAndGet();
175         if (hasCacheEventListeners()) {
176             for (ListenerWrapper listenerWrapper : cacheEventListeners) {
177                 if (listenerWrapper.getScope().shouldDeliver(remoteEvent)
178                         && !isCircularNotification(remoteEvent, listenerWrapper.getListener())) {
179                     CacheEventListener listener = listenerWrapper.getListener();
180                     invokeListener(listener, element, callback, Event.UPDATED);
181                 }
182             }
183         }
184     }
185
186     /**
187      * Notifies all registered listeners, in no guaranteed order, that an element has expired
188      *
189      * @param element     the Element to perform the notification on
190      * @param remoteEvent whether the event came from a remote cache peer
191      * @see CacheEventListener#notifyElementExpired
192      */

193     public final void notifyElementExpiry(Element element, boolean remoteEvent) {
194         internalNotifyElementExpiry(element, null, remoteEvent);
195     }
196
197     /**
198      * Notifies all registered listeners, in no guaranteed order, that an element has expired
199      *
200      * @param callback
201      * @param remoteEvent whether the event came from a remote cache peer
202      * @see CacheEventListener#notifyElementExpired
203      */

204
205     public final void notifyElementExpiry(ElementCreationCallback callback, boolean remoteEvent) {
206         internalNotifyElementExpiry(null, callback, remoteEvent);
207     }
208
209     private void internalNotifyElementExpiry(Element element, ElementCreationCallback callback, boolean remoteEvent) {
210         elementsExpiredCounter.incrementAndGet();
211         if (hasCacheEventListeners()) {
212             for (ListenerWrapper listenerWrapper : cacheEventListeners) {
213                 if (listenerWrapper.getScope().shouldDeliver(remoteEvent)
214                         && !isCircularNotification(remoteEvent, listenerWrapper.getListener())) {
215                     CacheEventListener listener = listenerWrapper.getListener();
216                     invokeListener(listener, element, callback, Event.EXPIRY);
217                 }
218             }
219         }
220     }
221
222     /**
223      * Returns whether or not at least one cache event listeners has been registered.
224      *
225      * @return true if a one or more listeners have registered, otherwise false
226      */

227     public final boolean hasCacheEventListeners() {
228         return cacheEventListeners.size() > 0;
229     }
230
231     /**
232      * Notifies all registered listeners, in no guaranteed order, that an element has been
233      * evicted from the cache
234      *
235      * @param element     the Element to perform the notification on
236      * @param remoteEvent whether the event came from a remote cache peer
237      * @see CacheEventListener#notifyElementEvicted
238      */

239     public final void notifyElementEvicted(Element element, boolean remoteEvent) {
240         internalNotifyElementEvicted(element, null, remoteEvent);
241     }
242
243     /**
244      * Notifies all registered listeners, in no guaranteed order, that an element has been
245      * evicted from the cache
246      *
247      * @param callback
248      * @param remoteEvent whether the event came from a remote cache peer
249      * @see CacheEventListener#notifyElementEvicted
250      */

251
252     public final void notifyElementEvicted(ElementCreationCallback callback, boolean remoteEvent) {
253         internalNotifyElementEvicted(null, callback, remoteEvent);
254     }
255
256     private void internalNotifyElementEvicted(Element element, ElementCreationCallback callback, boolean remoteEvent) {
257         if (cache.getCacheConfiguration().getPinningConfiguration() == null) {
258             elementsEvictedCounter.incrementAndGet();
259             if (hasCacheEventListeners()) {
260                 for (ListenerWrapper listenerWrapper : cacheEventListeners) {
261                     if (listenerWrapper.getScope().shouldDeliver(remoteEvent)
262                             && !isCircularNotification(remoteEvent, listenerWrapper.getListener())) {
263                         CacheEventListener listener = listenerWrapper.getListener();
264                         invokeListener(listener, element, callback, Event.EVICTED);
265                     }
266                 }
267             }
268         }
269     }
270
271     private void invokeListener(CacheEventListener listener, Element element, ElementCreationCallback callback, Event eventType) {
272         final Element e;
273
274         if (listener instanceof LiveCacheStatisticsData) {
275             // These listeners don't touch the element and since this is an internal listener there might not be
276             // an appropriate loader for resolving it
277             e = null;
278         } else if (callback != null) {
279             e = callback.createElement(listener.getClass().getClassLoader());
280         } else {
281             e = element;
282         }
283
284         notifyListener(listener, e, eventType);
285     }
286
287     private void notifyListener(CacheEventListener listener, Element element, Event eventType) {
288         switch (eventType) {
289             case EVICTED: {
290                 listener.notifyElementEvicted(cache, element);
291                 break;
292             }
293             case PUT: {
294                 listener.notifyElementPut(cache, element);
295                 break;
296             }
297             case EXPIRY: {
298                 listener.notifyElementExpired(cache, element);
299                 break;
300             }
301             case REMOVED: {
302                 listener.notifyElementRemoved(cache, element);
303                 break;
304             }
305             case UPDATED: {
306                 listener.notifyElementUpdated(cache, element);
307                 break;
308             }
309             default: {
310                 throw new AssertionError(eventType.toString());
311             }
312         }
313     }
314
315     /**
316      * Notifies all registered listeners, in no guaranteed order, that removeAll
317      * has been called and all elements cleared
318      *
319      * @param remoteEvent whether the event came from a remote cache peer
320      * @see CacheEventListener#notifyElementEvicted
321      */

322     public final void notifyRemoveAll(boolean remoteEvent) {
323         elementsRemoveAllCounter.incrementAndGet();
324         if (hasCacheEventListeners()) {
325             for (ListenerWrapper listenerWrapper : cacheEventListeners) {
326                 if (listenerWrapper.getScope().shouldDeliver(remoteEvent)
327                         && !isCircularNotification(remoteEvent, listenerWrapper.getListener())) {
328                     listenerWrapper.getListener().notifyRemoveAll(cache);
329                 }
330             }
331         }
332     }
333
334     /**
335      * CacheReplicators should not be notified of events received remotely, as this would cause
336      * a circular notification
337      *
338      * @param remoteEvent
339      * @param cacheEventListener
340      * @return true is notifiying the listener would cause a circular notification
341      */

342     private static boolean isCircularNotification(boolean remoteEvent, CacheEventListener cacheEventListener) {
343         return remoteEvent
344                 && (cacheEventListener instanceof CacheReplicator || cacheEventListener instanceof TerracottaCacheEventReplication);
345     }
346
347
348     /**
349      * Adds a listener to the notification service. No guarantee is made that listeners will be
350      * notified in the order they were added.
351      *
352      * @param cacheEventListener
353      * @return true if the listener is being added and was not already added
354      */

355     public final boolean registerListener(CacheEventListener cacheEventListener) {
356         return registerListener(cacheEventListener, NotificationScope.ALL);
357     }
358
359     /**
360      * Adds a listener to the notification service. No guarantee is made that listeners will be
361      * notified in the order they were added.
362      * <p/>
363      * If a cache is configured in a cluster, listeners in each node will get triggered by an event
364      * depending on the value of the <pre>listenFor</pre> parameter.
365      *
366      * @param cacheEventListener The listener to add
367      * @param scope              The notification scope
368      * @return true if the listener is being added and was not already added
369      * @since 2.0
370      */

371     public final boolean registerListener(CacheEventListener cacheEventListener, NotificationScope scope) {
372         if (cacheEventListener == null) {
373             return false;
374         }
375         boolean result = cacheEventListeners.add(new ListenerWrapper(cacheEventListener, scope));
376         if (result && cacheEventListener instanceof CacheReplicator) {
377             this.hasReplicator.set(true);
378         }
379         return result;
380     }
381
382     /**
383      * Removes a listener from the notification service.
384      *
385      * @param cacheEventListener
386      * @return true if the listener was present
387      */

388     public final boolean unregisterListener(CacheEventListener cacheEventListener) {
389         boolean result = false;
390         int cacheReplicators = 0;
391         Iterator<ListenerWrapper> it = cacheEventListeners.iterator();
392         while (it.hasNext()) {
393             ListenerWrapper listenerWrapper = it.next();
394             if (listenerWrapper.getListener().equals(cacheEventListener)) {
395                 cacheEventListeners.remove(listenerWrapper);
396                 result = true;
397             } else {
398                 if (listenerWrapper.getListener() instanceof CacheReplicator) {
399                     cacheReplicators++;
400                 }
401             }
402         }
403
404         if (cacheReplicators > 0) {
405             hasReplicator.set(true);
406         } else {
407             hasReplicator.set(false);
408         }
409
410         return result;
411     }
412
413     /**
414      * Determines whether any registered listeners are CacheReplicators.
415      *
416      * @return whether any registered listeners are CacheReplicators.
417      */

418     public final boolean hasCacheReplicators() {
419         return hasReplicator.get();
420     }
421
422     /**
423      * Gets a copy of the set of the listeners registered to this class
424      *
425      * @return a set of type <code>CacheEventListener</code>
426      */

427     public final Set<CacheEventListener> getCacheEventListeners() {
428         Set<CacheEventListener> listenerSet = new HashSet<CacheEventListener>();
429         for (ListenerWrapper listenerWrapper : cacheEventListeners) {
430             listenerSet.add(listenerWrapper.getListener());
431         }
432         return listenerSet;
433     }
434
435     /**
436      * Tell listeners to dispose themselves.
437      * Because this method is only ever called from a synchronized cache method, it does not itself need to be
438      * synchronized.
439      */

440     public final void dispose() {
441         for (ListenerWrapper listenerWrapper : cacheEventListeners) {
442             listenerWrapper.getListener().dispose();
443         }
444         cacheEventListeners.clear();
445     }
446
447     /**
448      * Returns a string representation of the object. In general, the
449      * <code>toString</code> method returns a string that
450      * "textually represents" this object. The result should
451      * be a concise but informative representation that is easy for a
452      * person to read.
453      *
454      * @return a string representation of the object.
455      */

456     @Override
457     public final String toString() {
458         StringBuilder sb = new StringBuilder(" cacheEventListeners: ");
459         for (ListenerWrapper listenerWrapper : cacheEventListeners) {
460             sb.append(listenerWrapper.getListener().getClass().getName()).append(" ");
461         }
462         return sb.toString();
463     }
464
465     /**
466      * Clears all event counters
467      */

468     public void clearCounters() {
469         elementsRemovedCounter.set(0);
470         elementsPutCounter.set(0);
471         elementsUpdatedCounter.set(0);
472         elementsExpiredCounter.set(0);
473         elementsEvictedCounter.set(0);
474         elementsRemoveAllCounter.set(0);
475     }
476
477     /**
478      * Gets the number of events, irrespective of whether there are any registered listeners.
479      *
480      * @return the number of events since cache creation or last clearing of counters
481      */

482     public long getElementsRemovedCounter() {
483         return elementsRemovedCounter.get();
484     }
485
486     /**
487      * Gets the number of events, irrespective of whether there are any registered listeners.
488      *
489      * @return the number of events since cache creation or last clearing of counters
490      */

491     public long getElementsPutCounter() {
492         return elementsPutCounter.get();
493     }
494
495     /**
496      * Gets the number of events, irrespective of whether there are any registered listeners.
497      *
498      * @return the number of events since cache creation or last clearing of counters
499      */

500     public long getElementsUpdatedCounter() {
501         return elementsUpdatedCounter.get();
502     }
503
504     /**
505      * Gets the number of events, irrespective of whether there are any registered listeners.
506      *
507      * @return the number of events since cache creation or last clearing of counters
508      */

509     public long getElementsExpiredCounter() {
510         return elementsExpiredCounter.get();
511     }
512
513     /**
514      * Gets the number of events, irrespective of whether there are any registered listeners.
515      *
516      * @return the number of events since cache creation or last clearing of counters
517      */

518     public long getElementsEvictedCounter() {
519         return elementsEvictedCounter.get();
520     }
521
522     /**
523      * Gets the number of events, irrespective of whether there are any registered listeners.
524      *
525      * @return the number of events since cache creation or last clearing of counters
526      */

527     public long getElementsRemoveAllCounter() {
528         return elementsRemoveAllCounter.get();
529     }
530
531     /**
532      * Returns the {@link FrontEndCacheTier} this RegisteredEventListeners is backing, or null if the cache isn't backed by one
533      * @return the Cache's FrontEndCacheTier
534      */

535     public FrontEndCacheTier getFrontEndCacheTier() {
536         Store store;
537         try {
538             store = helper.getStore();
539         } catch (IllegalStateException e) {
540             return null;
541         }
542         return store instanceof FrontEndCacheTier ? (FrontEndCacheTier) store : null;
543     }
544
545     /**
546      * Combine a Listener and its NotificationScope.  Equality and hashcode are based purely on the listener.
547      * This implies that the same listener cannot be added to the set of registered listeners more than
548      * once with different notification scopes.
549      *
550      * @author Alex Miller
551      */

552     private static final class ListenerWrapper {
553         private final CacheEventListener listener;
554         private final NotificationScope scope;
555
556         private ListenerWrapper(CacheEventListener listener, NotificationScope scope) {
557             this.listener = listener;
558             this.scope = scope;
559         }
560
561         private CacheEventListener getListener() {
562             return this.listener;
563         }
564
565         private NotificationScope getScope() {
566             return this.scope;
567         }
568
569         /**
570          * Hash code based on listener
571          *
572          * @see java.lang.Object#hashCode()
573          */

574         @Override
575         public int hashCode() {
576             return listener.hashCode();
577         }
578
579         /**
580          * Equals based on listener (NOT based on scope) - can't have same listener with two different scopes
581          *
582          * @see java.lang.Object#equals(java.lang.Object)
583          */

584         @Override
585         public boolean equals(Object obj) {
586             if (this == obj) {
587                 return true;
588             }
589             if (obj == null) {
590                 return false;
591             }
592             if (getClass() != obj.getClass()) {
593                 return false;
594             }
595             ListenerWrapper other = (ListenerWrapper) obj;
596             if (listener == null) {
597                 if (other.listener != null) {
598                     return false;
599                 }
600             } else if (!listener.equals(other.listener)) {
601                 return false;
602             }
603             return true;
604         }
605
606         @Override
607         public String toString() {
608             return listener.toString();
609         }
610     }
611
612     /**
613      * Callback interface for creating elements to pass to registered listeners.
614      * For replicated/clustered caches the event notification thread that receives
615      * events from the network might not have the correct context for resolving
616      * cache values
617      *
618      * @author teck
619      *
620      */

621     public interface ElementCreationCallback {
622         /**
623          * Materialize the relevant element in the given classloader
624          *
625          * @param loader
626          * @return element
627          */

628         Element createElement(ClassLoader loader);
629     }
630
631     /**
632      * Event callback types
633      */

634     private static enum Event {
635         EVICTED, PUT, EXPIRY, UPDATED, REMOVED;
636     }
637
638
639 }
640