1 /*
2 * Copyright 1999-2007 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26 package javax.management;
27
28 import java.util.Collections;
29 import java.util.List;
30 import java.util.concurrent.CopyOnWriteArrayList;
31 import java.util.concurrent.Executor;
32
33 import com.sun.jmx.remote.util.ClassLogger;
34
35 /**
36 * <p>Provides an implementation of {@link
37 * javax.management.NotificationEmitter NotificationEmitter}
38 * interface. This can be used as the super class of an MBean that
39 * sends notifications.</p>
40 *
41 * <p>By default, the notification dispatch model is synchronous.
42 * That is, when a thread calls sendNotification, the
43 * <code>NotificationListener.handleNotification</code> method of each listener
44 * is called within that thread. You can override this default
45 * by overriding <code>handleNotification</code> in a subclass, or by passing an
46 * Executor to the constructor.</p>
47 *
48 * <p>If the method call of a filter or listener throws an {@link Exception},
49 * then that exception does not prevent other listeners from being invoked. However,
50 * if the method call of a filter or of {@code Executor.execute} or of
51 * {@code handleNotification} (when no {@code Excecutor} is specified) throws an
52 * {@link Error}, then that {@code Error} is propagated to the caller of
53 * {@link #sendNotification sendNotification}.</p>
54 *
55 * <p>Remote listeners added using the JMX Remote API (see JMXConnector) are not
56 * usually called synchronously. That is, when sendNotification returns, it is
57 * not guaranteed that any remote listeners have yet received the notification.</p>
58 *
59 * @since 1.5
60 */
61 public class NotificationBroadcasterSupport implements NotificationEmitter {
62 /**
63 * Constructs a NotificationBroadcasterSupport where each listener is invoked by the
64 * thread sending the notification. This constructor is equivalent to
65 * {@link NotificationBroadcasterSupport#NotificationBroadcasterSupport(Executor,
66 * MBeanNotificationInfo[] info) NotificationBroadcasterSupport(null, null)}.
67 */
68 public NotificationBroadcasterSupport() {
69 this(null, (MBeanNotificationInfo[]) null);
70 }
71
72 /**
73 * Constructs a NotificationBroadcasterSupport where each listener is invoked using
74 * the given {@link java.util.concurrent.Executor}. When {@link #sendNotification
75 * sendNotification} is called, a listener is selected if it was added with a null
76 * {@link NotificationFilter}, or if {@link NotificationFilter#isNotificationEnabled
77 * isNotificationEnabled} returns true for the notification being sent. The call to
78 * <code>NotificationFilter.isNotificationEnabled</code> takes place in the thread
79 * that called <code>sendNotification</code>. Then, for each selected listener,
80 * {@link Executor#execute executor.execute} is called with a command
81 * that calls the <code>handleNotification</code> method.
82 * This constructor is equivalent to
83 * {@link NotificationBroadcasterSupport#NotificationBroadcasterSupport(Executor,
84 * MBeanNotificationInfo[] info) NotificationBroadcasterSupport(executor, null)}.
85 * @param executor an executor used by the method <code>sendNotification</code> to
86 * send each notification. If it is null, the thread calling <code>sendNotification</code>
87 * will invoke the <code>handleNotification</code> method itself.
88 * @since 1.6
89 */
90 public NotificationBroadcasterSupport(Executor executor) {
91 this(executor, (MBeanNotificationInfo[]) null);
92 }
93
94 /**
95 * <p>Constructs a NotificationBroadcasterSupport with information
96 * about the notifications that may be sent. Each listener is
97 * invoked by the thread sending the notification. This
98 * constructor is equivalent to {@link
99 * NotificationBroadcasterSupport#NotificationBroadcasterSupport(Executor,
100 * MBeanNotificationInfo[] info)
101 * NotificationBroadcasterSupport(null, info)}.</p>
102 *
103 * <p>If the <code>info</code> array is not empty, then it is
104 * cloned by the constructor as if by {@code info.clone()}, and
105 * each call to {@link #getNotificationInfo()} returns a new
106 * clone.</p>
107 *
108 * @param info an array indicating, for each notification this
109 * MBean may send, the name of the Java class of the notification
110 * and the notification type. Can be null, which is equivalent to
111 * an empty array.
112 *
113 * @since 1.6
114 */
115 public NotificationBroadcasterSupport(MBeanNotificationInfo... info) {
116 this(null, info);
117 }
118
119 /**
120 * <p>Constructs a NotificationBroadcasterSupport with information about the notifications that may be sent,
121 * and where each listener is invoked using the given {@link java.util.concurrent.Executor}.</p>
122 *
123 * <p>When {@link #sendNotification sendNotification} is called, a
124 * listener is selected if it was added with a null {@link
125 * NotificationFilter}, or if {@link
126 * NotificationFilter#isNotificationEnabled isNotificationEnabled}
127 * returns true for the notification being sent. The call to
128 * <code>NotificationFilter.isNotificationEnabled</code> takes
129 * place in the thread that called
130 * <code>sendNotification</code>. Then, for each selected
131 * listener, {@link Executor#execute executor.execute} is called
132 * with a command that calls the <code>handleNotification</code>
133 * method.</p>
134 *
135 * <p>If the <code>info</code> array is not empty, then it is
136 * cloned by the constructor as if by {@code info.clone()}, and
137 * each call to {@link #getNotificationInfo()} returns a new
138 * clone.</p>
139 *
140 * @param executor an executor used by the method
141 * <code>sendNotification</code> to send each notification. If it
142 * is null, the thread calling <code>sendNotification</code> will
143 * invoke the <code>handleNotification</code> method itself.
144 *
145 * @param info an array indicating, for each notification this
146 * MBean may send, the name of the Java class of the notification
147 * and the notification type. Can be null, which is equivalent to
148 * an empty array.
149 *
150 * @since 1.6
151 */
152 public NotificationBroadcasterSupport(Executor executor,
153 MBeanNotificationInfo... info) {
154 this.executor = (executor != null) ? executor : defaultExecutor;
155
156 notifInfo = info == null ? NO_NOTIFICATION_INFO : info.clone();
157 }
158
159 /**
160 * Adds a listener.
161 *
162 * @param listener The listener to receive notifications.
163 * @param filter The filter object. If filter is null, no
164 * filtering will be performed before handling notifications.
165 * @param handback An opaque object to be sent back to the
166 * listener when a notification is emitted. This object cannot be
167 * used by the Notification broadcaster object. It should be
168 * resent unchanged with the notification to the listener.
169 *
170 * @exception IllegalArgumentException thrown if the listener is null.
171 *
172 * @see #removeNotificationListener
173 */
174 public void addNotificationListener(NotificationListener listener,
175 NotificationFilter filter,
176 Object handback) {
177
178 if (listener == null) {
179 throw new IllegalArgumentException ("Listener can't be null") ;
180 }
181
182 listenerList.add(new ListenerInfo(listener, filter, handback));
183 }
184
185 public void removeNotificationListener(NotificationListener listener)
186 throws ListenerNotFoundException {
187
188 ListenerInfo wildcard = new WildcardListenerInfo(listener);
189 boolean removed =
190 listenerList.removeAll(Collections.singleton(wildcard));
191 if (!removed)
192 throw new ListenerNotFoundException("Listener not registered");
193 }
194
195 public void removeNotificationListener(NotificationListener listener,
196 NotificationFilter filter,
197 Object handback)
198 throws ListenerNotFoundException {
199
200 ListenerInfo li = new ListenerInfo(listener, filter, handback);
201 boolean removed = listenerList.remove(li);
202 if (!removed) {
203 throw new ListenerNotFoundException("Listener not registered " +
204 "(with this filter and " +
205 "handback)");
206 // or perhaps not registered at all
207 }
208 }
209
210 public MBeanNotificationInfo[] getNotificationInfo() {
211 if (notifInfo.length == 0)
212 return notifInfo;
213 else
214 return notifInfo.clone();
215 }
216
217
218 /**
219 * Sends a notification.
220 *
221 * If an {@code Executor} was specified in the constructor, it will be given one
222 * task per selected listener to deliver the notification to that listener.
223 *
224 * @param notification The notification to send.
225 */
226 public void sendNotification(Notification notification) {
227
228 if (notification == null) {
229 return;
230 }
231
232 boolean enabled;
233
234 for (ListenerInfo li : listenerList) {
235 try {
236 enabled = li.filter == null ||
237 li.filter.isNotificationEnabled(notification);
238 } catch (Exception e) {
239 if (logger.debugOn()) {
240 logger.debug("sendNotification", e);
241 }
242
243 continue;
244 }
245
246 if (enabled) {
247 executor.execute(new SendNotifJob(notification, li));
248 }
249 }
250 }
251
252 /**
253 * <p>This method is called by {@link #sendNotification
254 * sendNotification} for each listener in order to send the
255 * notification to that listener. It can be overridden in
256 * subclasses to change the behavior of notification delivery,
257 * for instance to deliver the notification in a separate
258 * thread.</p>
259 *
260 * <p>The default implementation of this method is equivalent to
261 * <pre>
262 * listener.handleNotification(notif, handback);
263 * </pre>
264 *
265 * @param listener the listener to which the notification is being
266 * delivered.
267 * @param notif the notification being delivered to the listener.
268 * @param handback the handback object that was supplied when the
269 * listener was added.
270 *
271 */
272 protected void handleNotification(NotificationListener listener,
273 Notification notif, Object handback) {
274 listener.handleNotification(notif, handback);
275 }
276
277 // private stuff
278 private static class ListenerInfo {
279 NotificationListener listener;
280 NotificationFilter filter;
281 Object handback;
282
283 ListenerInfo(NotificationListener listener,
284 NotificationFilter filter,
285 Object handback) {
286 this.listener = listener;
287 this.filter = filter;
288 this.handback = handback;
289 }
290
291 public boolean equals(Object o) {
292 if (!(o instanceof ListenerInfo))
293 return false;
294 ListenerInfo li = (ListenerInfo) o;
295 if (li instanceof WildcardListenerInfo)
296 return (li.listener == listener);
297 else
298 return (li.listener == listener && li.filter == filter
299 && li.handback == handback);
300 }
301 }
302
303 private static class WildcardListenerInfo extends ListenerInfo {
304 WildcardListenerInfo(NotificationListener listener) {
305 super(listener, null, null);
306 }
307
308 public boolean equals(Object o) {
309 assert (!(o instanceof WildcardListenerInfo));
310 return o.equals(this);
311 }
312 }
313
314 private List<ListenerInfo> listenerList =
315 new CopyOnWriteArrayList<ListenerInfo>();
316
317 // since 1.6
318 private final Executor executor;
319 private final MBeanNotificationInfo[] notifInfo;
320
321 private final static Executor defaultExecutor = new Executor() {
322 // DirectExecutor using caller thread
323 public void execute(Runnable r) {
324 r.run();
325 }
326 };
327
328 private static final MBeanNotificationInfo[] NO_NOTIFICATION_INFO =
329 new MBeanNotificationInfo[0];
330
331 private class SendNotifJob implements Runnable {
332 public SendNotifJob(Notification notif, ListenerInfo listenerInfo) {
333 this.notif = notif;
334 this.listenerInfo = listenerInfo;
335 }
336
337 public void run() {
338 try {
339 handleNotification(listenerInfo.listener,
340 notif, listenerInfo.handback);
341 } catch (Exception e) {
342 if (logger.debugOn()) {
343 logger.debug("SendNotifJob-run", e);
344 }
345 }
346 }
347
348 private final Notification notif;
349 private final ListenerInfo listenerInfo;
350 }
351
352 private static final ClassLogger logger =
353 new ClassLogger("javax.management", "NotificationBroadcasterSupport");
354 }