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 }