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