001: /*
002: * Copyright (C) The MX4J Contributors.
003: * All rights reserved.
004: *
005: * This software is distributed under the terms of the MX4J License version 1.0.
006: * See the terms of the MX4J License in the documentation provided with this software.
007: */
008:
009: package javax.management;
010:
011: import java.util.ArrayList;
012: import java.util.HashMap;
013: import java.util.Iterator;
014:
015: import mx4j.log.Log;
016: import mx4j.log.Logger;
017:
018: /**
019: * Provides an implementation of NotificationEmitter interface.
020: * This can be used as the super class of an MBean that sends notifications.
021: * It is not specified whether the notification dispatch model is synchronous or asynchronous.
022: * That is, when a thread calls sendNotification, the NotificationListener.handleNotification
023: * method of each listener may be called within that thread (a synchronous model)
024: * or within some other thread (an asynchronous model).
025: * Applications should not depend on notification dispatch being synchronous or being asynchronous. Thus:
026: * <ul>
027: * <li>Applications should not assume a synchronous model. When the sendNotification method returns,
028: * it is not guaranteed that every listener's handleNotification method has been called.
029: * It is not guaranteed either that a listener will see notifications in the same order as they were generated.
030: * Listeners that depend on order should use the sequence number of notifications to determine their order
031: * (see Notification.getSequenceNumber()).</li>
032: * <li>Applications should not assume an asynchronous model.
033: * If the actions performed by a listener are potentially slow, the listener should arrange for them to be performed
034: * in another thread, to avoid holding up other listeners and the caller of sendNotification.</li>
035: * </ul>
036: *
037: * @version $Revision: 1.19 $
038: */
039: public class NotificationBroadcasterSupport implements
040: NotificationEmitter {
041: private static final NotificationFilter NULL_FILTER = new NotificationFilter() {
042: public boolean isNotificationEnabled(Notification notification) {
043: return true;
044: }
045:
046: public String toString() {
047: return "null filter";
048: }
049: };
050:
051: private static final Object NULL_HANDBACK = new Object() {
052: public String toString() {
053: return "null handback";
054: }
055: };
056:
057: private HashMap m_listeners = new HashMap();
058:
059: public NotificationBroadcasterSupport() {
060: }
061:
062: private Logger getLogger() {
063: return Log.getLogger(getClass().getName());
064: }
065:
066: public void addNotificationListener(NotificationListener listener,
067: NotificationFilter filter, Object handback) {
068: Logger logger = getLogger();
069:
070: if (logger.isEnabledFor(Logger.TRACE))
071: logger.trace("Adding notification listener: " + listener
072: + ", filter: " + filter + ", handback: " + handback
073: + " to " + this );
074:
075: if (listener == null)
076: throw new IllegalArgumentException(
077: "Notification listener cannot be null");
078:
079: // Normalize the arguments
080: if (filter == null)
081: filter = NULL_FILTER;
082: if (handback == null)
083: handback = NULL_HANDBACK;
084:
085: FilterHandbackPair pair = new FilterHandbackPair(filter,
086: handback);
087:
088: synchronized (this ) {
089: ArrayList pairs = (ArrayList) m_listeners.get(listener);
090:
091: if (pairs == null) {
092: // A new listener, register it
093: pairs = new ArrayList();
094: pairs.add(pair);
095: m_listeners.put(listener, pairs);
096: } else {
097: // Check that the same triple (listener, filter, handback) is not already registered
098: for (int i = 0; i < pairs.size(); ++i) {
099: FilterHandbackPair other = (FilterHandbackPair) pairs
100: .get(i);
101: if (pair.filter.equals(other.filter)
102: && pair.handback.equals(other.handback)) {
103: // Same filter and same handback for the same listener, it's already registered
104: throw new RuntimeOperationsException(
105: new IllegalArgumentException(
106: "Notification listener is already registered"));
107: }
108: }
109: // Not yet registered, register.
110: // Do not merge this call with the one in the if branch: like this is easier to debug
111: // (I know the if-else branch from where I'm coming)
112: pairs.add(pair);
113: }
114:
115: if (logger.isEnabledFor(Logger.TRACE))
116: logger.trace("Filters - Handbacks for this listener: "
117: + pairs);
118: }
119:
120: if (logger.isEnabledFor(Logger.TRACE))
121: logger.trace("Notification listener added successfully to "
122: + this );
123: }
124:
125: public void removeNotificationListener(NotificationListener listener)
126: throws ListenerNotFoundException {
127: Logger logger = getLogger();
128: if (logger.isEnabledFor(Logger.TRACE))
129: logger.trace("Removing notification listener: " + listener);
130:
131: int removed = removeNotificationListenerImpl(listener, null,
132: null);
133:
134: if (logger.isEnabledFor(Logger.TRACE))
135: logger
136: .trace(removed
137: + " notification listener(s) removed successfully from "
138: + this );
139: }
140:
141: public void removeNotificationListener(
142: NotificationListener listener, NotificationFilter filter,
143: Object handback) throws ListenerNotFoundException {
144: Logger logger = getLogger();
145: if (logger.isEnabledFor(Logger.TRACE))
146: logger
147: .trace("Removing notification listener: "
148: + listener + ", filter: " + filter
149: + ", handback: " + handback);
150:
151: // Normalize the arguments if necessary
152: if (filter == null)
153: filter = NULL_FILTER;
154: if (handback == null)
155: handback = NULL_HANDBACK;
156:
157: int removed = removeNotificationListenerImpl(listener, filter,
158: handback);
159:
160: if (logger.isEnabledFor(Logger.TRACE))
161: logger
162: .trace(removed
163: + " notification listener(s) removed successfully from "
164: + this );
165: }
166:
167: private int removeNotificationListenerImpl(
168: NotificationListener listener, NotificationFilter filter,
169: Object handback) throws ListenerNotFoundException {
170: Logger logger = getLogger();
171: synchronized (this ) {
172: if (logger.isEnabledFor(Logger.TRACE))
173: logger.trace("Listeners for " + this + " are: "
174: + m_listeners);
175:
176: ArrayList pairs = (ArrayList) m_listeners.get(listener);
177:
178: if (pairs == null)
179: throw new ListenerNotFoundException(
180: "NotificationListener " + listener
181: + " not found");
182:
183: if (filter == null) {
184: if (handback == null) {
185: // Means I want to remove all triplets for this listener
186:
187: ArrayList removed = (ArrayList) m_listeners
188: .remove(listener);
189: return removed.size();
190: } else {
191: // Means I want to remove all triplets with the given handback for this listener
192:
193: int count = 0;
194: for (int i = 0; i < pairs.size(); ++i) {
195: Object hand = ((FilterHandbackPair) pairs
196: .get(i)).handback;
197: if (handback.equals(hand)) {
198: pairs.remove(i);
199: ++count;
200: }
201: }
202: if (count == 0)
203: throw new ListenerNotFoundException(
204: "NotificationListener " + listener
205: + " with handback " + handback
206: + " not found");
207:
208: // Check if it was the last listener
209: if (pairs.isEmpty())
210: m_listeners.remove(listener);
211:
212: return count;
213: }
214: } else {
215: if (handback == null) {
216: // Means I want to remove all triplets with the given filter for this listener
217:
218: int count = 0;
219: for (int i = 0; i < pairs.size(); ++i) {
220: Object filt = ((FilterHandbackPair) pairs
221: .get(i)).filter;
222: if (filter.equals(filt)) {
223: pairs.remove(i);
224: ++count;
225: }
226: }
227: if (count == 0)
228: throw new ListenerNotFoundException(
229: "NotificationListener " + listener
230: + " with filter " + filter
231: + " not found");
232:
233: // Check if it was the last listener
234: if (pairs.isEmpty())
235: m_listeners.remove(listener);
236:
237: return count;
238: } else {
239: // Means I want to remove all triplets with the given filter and handback for this listener
240:
241: int count = 0;
242: for (int i = 0; i < pairs.size(); ++i) {
243: FilterHandbackPair pair = (FilterHandbackPair) pairs
244: .get(i);
245: if (filter.equals(pair.filter)
246: && handback.equals(pair.handback)) {
247: pairs.remove(i);
248: ++count;
249: }
250: }
251: if (count == 0)
252: throw new ListenerNotFoundException(
253: "NotificationListener " + listener
254: + " with filter " + filter
255: + " and handback " + handback
256: + " not found");
257:
258: // Check if it was the last listener
259: if (pairs.isEmpty())
260: m_listeners.remove(listener);
261:
262: return count;
263: }
264: }
265: }
266: }
267:
268: public MBeanNotificationInfo[] getNotificationInfo() {
269: // Subclasses should override returning more informations
270: return new MBeanNotificationInfo[0];
271: }
272:
273: /**
274: * Sends the given notification to all registered listeners
275: *
276: * @param notification The notification to send
277: */
278: public void sendNotification(Notification notification) {
279: Logger logger = getLogger();
280: boolean trace = logger.isEnabledFor(Logger.TRACE);
281: boolean info = logger.isEnabledFor(Logger.INFO);
282:
283: HashMap listeners = null;
284:
285: synchronized (this ) {
286: // Clone the listeners, so we can notify without holding any lock
287: // It is a shallow copy, below we will clone the pairs as well
288: // I don't care if in the middle someone else adds or remove other pairs
289: listeners = (HashMap) m_listeners.clone();
290: }
291:
292: // Loop over all listeners
293: Iterator i = listeners.keySet().iterator();
294:
295: if (i.hasNext() && trace)
296: logger.trace("Sending notifications from " + this );
297:
298: while (i.hasNext()) {
299: NotificationListener listener = (NotificationListener) i
300: .next();
301: if (trace)
302: logger.trace("\tListener is: " + listener);
303:
304: // Clone again the pairs for this listener.
305: // I freezed the listeners with the first clone, if someone removes a pair
306: // in the middle of notifications I don't care: here I clone the actual pairs
307: ArrayList pairs = null;
308: synchronized (this ) {
309: pairs = (ArrayList) listeners.get(listener);
310: pairs = (ArrayList) pairs.clone();
311: }
312:
313: if (trace)
314: logger.trace("\tFilters - Handback for this listener: "
315: + pairs);
316:
317: // Loop over the same listener that registered many times with different filter / handbacks
318: for (int j = 0; j < pairs.size(); ++j) {
319: FilterHandbackPair pair = (FilterHandbackPair) pairs
320: .get(j);
321:
322: NotificationFilter filter = pair.filter;
323: Object handback = pair.handback;
324:
325: // Denormalize filter and handback if necessary
326: if (filter == NULL_FILTER)
327: filter = null;
328: if (handback == NULL_HANDBACK)
329: handback = null;
330:
331: boolean enabled = false;
332: try {
333: enabled = filter == null
334: || filter
335: .isNotificationEnabled(notification);
336: } catch (Throwable x) {
337: if (info)
338: logger
339: .info(
340: "Throwable caught from isNotificationEnabled",
341: x);
342: // And go on
343: }
344:
345: if (trace)
346: logger.trace("\t\tFilter is: " + filter
347: + ", enabled: " + enabled);
348:
349: if (enabled) {
350: if (trace) {
351: logger.debug("\t\tHandback is: " + handback);
352: logger.debug("\t\tSending notification "
353: + notification);
354: }
355:
356: try {
357: handleNotification(listener, notification,
358: handback);
359: } catch (Throwable x) {
360: if (info)
361: logger
362: .info(
363: "Throwable caught from handleNotification",
364: x);
365: // And go on with next listener
366: }
367: }
368: }
369: }
370: }
371:
372: /**
373: * This method is called by {@link #sendNotification} for each listener in order to send the notification to that listener.
374: * It can be overridden in subclasses to change the behaviour of notification delivery,
375: * for instance to deliver the notification in a separate thread.
376: * It is not guaranteed that this method is called by the same thread as the one that called sendNotification.
377: * The default implementation of this method is equivalent to
378: * <code>listener.handleNotification(notif, handback);</code>
379: *
380: * @param listener - the listener to which the notification is being delivered.
381: * @param notification - the notification being delivered to the listener.
382: * @param handback - the handback object that was supplied when the listener was added.
383: * @since JMX 1.2
384: */
385: protected void handleNotification(NotificationListener listener,
386: Notification notification, Object handback) {
387: listener.handleNotification(notification, handback);
388: }
389:
390: private static class FilterHandbackPair {
391: private NotificationFilter filter;
392: private Object handback;
393:
394: private FilterHandbackPair(NotificationFilter filter,
395: Object handback) {
396: this.filter = filter;
397: this.handback = handback;
398: }
399: }
400: }
|