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