|
1 /* |
|
2 * Copyright 2008 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.namespace; |
|
27 |
|
28 import com.sun.jmx.remote.util.ClassLogger; |
|
29 import java.util.ArrayList; |
|
30 import java.util.HashMap; |
|
31 import java.util.Iterator; |
|
32 import java.util.List; |
|
33 import java.util.Map; |
|
34 import javax.management.InstanceNotFoundException; |
|
35 import javax.management.ListenerNotFoundException; |
|
36 import javax.management.MBeanNotificationInfo; |
|
37 import javax.management.Notification; |
|
38 import javax.management.NotificationEmitter; |
|
39 import javax.management.NotificationFilter; |
|
40 import javax.management.NotificationListener; |
|
41 import javax.management.ObjectName; |
|
42 import javax.management.event.EventConsumer; |
|
43 |
|
44 /** |
|
45 * <p>This class maintains a list of subscribers for ObjectName patterns and |
|
46 * allows a notification to be sent to all subscribers for a given ObjectName. |
|
47 * It is typically used in conjunction with {@link MBeanServerSupport} |
|
48 * to implement a namespace with Virtual MBeans that can emit notifications. |
|
49 * The {@code VirtualEventManager} keeps track of the listeners that have been |
|
50 * added to each Virtual MBean. When an event occurs that should trigger a |
|
51 * notification from a Virtual MBean, the {@link #publish publish} method can |
|
52 * be used to send it to the appropriate listeners.</p> |
|
53 * @since 1.7 |
|
54 */ |
|
55 public class VirtualEventManager implements EventConsumer { |
|
56 /** |
|
57 * <p>Create a new {@code VirtualEventManager}.</p> |
|
58 */ |
|
59 public VirtualEventManager() { |
|
60 } |
|
61 |
|
62 public void subscribe( |
|
63 ObjectName name, |
|
64 NotificationListener listener, |
|
65 NotificationFilter filter, |
|
66 Object handback) { |
|
67 |
|
68 if (logger.traceOn()) |
|
69 logger.trace("subscribe", "" + name); |
|
70 |
|
71 if (name == null) |
|
72 throw new IllegalArgumentException("Null MBean name"); |
|
73 |
|
74 if (listener == null) |
|
75 throw new IllegalArgumentException("Null listener"); |
|
76 |
|
77 Map<ObjectName, List<ListenerInfo>> map = |
|
78 name.isPattern() ? patternSubscriptionMap : exactSubscriptionMap; |
|
79 |
|
80 final ListenerInfo li = new ListenerInfo(listener, filter, handback); |
|
81 List<ListenerInfo> list; |
|
82 |
|
83 synchronized (map) { |
|
84 list = map.get(name); |
|
85 if (list == null) { |
|
86 list = new ArrayList<ListenerInfo>(); |
|
87 map.put(name, list); |
|
88 } |
|
89 list.add(li); |
|
90 } |
|
91 } |
|
92 |
|
93 public void unsubscribe( |
|
94 ObjectName name, NotificationListener listener) |
|
95 throws ListenerNotFoundException { |
|
96 |
|
97 if (logger.traceOn()) |
|
98 logger.trace("unsubscribe2", "" + name); |
|
99 |
|
100 if (name == null) |
|
101 throw new IllegalArgumentException("Null MBean name"); |
|
102 |
|
103 if (listener == null) |
|
104 throw new ListenerNotFoundException(); |
|
105 |
|
106 Map<ObjectName, List<ListenerInfo>> map = |
|
107 name.isPattern() ? patternSubscriptionMap : exactSubscriptionMap; |
|
108 |
|
109 final ListenerInfo li = new ListenerInfo(listener, null, null); |
|
110 List<ListenerInfo> list; |
|
111 synchronized (map) { |
|
112 list = map.get(name); |
|
113 if (list == null || !list.remove(li)) |
|
114 throw new ListenerNotFoundException(); |
|
115 |
|
116 if (list.isEmpty()) |
|
117 map.remove(name); |
|
118 } |
|
119 } |
|
120 |
|
121 /** |
|
122 * <p>Unsubscribes a listener which is listening to an MBean or a set of |
|
123 * MBeans represented by an {@code ObjectName} pattern.</p> |
|
124 * |
|
125 * <p>The listener to be removed must have been added by the {@link |
|
126 * #subscribe subscribe} method with the given {@code name}, {@code filter}, |
|
127 * and {@code handback}. If the {@code |
|
128 * name} is a pattern, then the {@code subscribe} must have used the same |
|
129 * pattern. If the same listener has been subscribed more than once to the |
|
130 * {@code name} with the same filter and handback, only one listener is |
|
131 * removed.</p> |
|
132 * |
|
133 * @param name The name of the MBean or an {@code ObjectName} pattern |
|
134 * representing a set of MBeans to which the listener was subscribed. |
|
135 * @param listener A listener that was previously subscribed to the |
|
136 * MBean(s). |
|
137 * |
|
138 * @throws ListenerNotFoundException The given {@code listener} was not |
|
139 * subscribed to the given {@code name}. |
|
140 * |
|
141 * @see #subscribe |
|
142 */ |
|
143 public void unsubscribe( |
|
144 ObjectName name, NotificationListener listener, |
|
145 NotificationFilter filter, Object handback) |
|
146 throws ListenerNotFoundException { |
|
147 |
|
148 if (logger.traceOn()) |
|
149 logger.trace("unsubscribe4", "" + name); |
|
150 |
|
151 if (name == null) |
|
152 throw new IllegalArgumentException("Null MBean name"); |
|
153 |
|
154 if (listener == null) |
|
155 throw new ListenerNotFoundException(); |
|
156 |
|
157 Map<ObjectName, List<ListenerInfo>> map = |
|
158 name.isPattern() ? patternSubscriptionMap : exactSubscriptionMap; |
|
159 |
|
160 List<ListenerInfo> list; |
|
161 synchronized (map) { |
|
162 list = map.get(name); |
|
163 boolean removed = false; |
|
164 for (Iterator<ListenerInfo> it = list.iterator(); it.hasNext(); ) { |
|
165 ListenerInfo li = it.next(); |
|
166 if (li.equals(listener, filter, handback)) { |
|
167 it.remove(); |
|
168 removed = true; |
|
169 break; |
|
170 } |
|
171 } |
|
172 if (!removed) |
|
173 throw new ListenerNotFoundException(); |
|
174 |
|
175 if (list.isEmpty()) |
|
176 map.remove(name); |
|
177 } |
|
178 } |
|
179 |
|
180 /** |
|
181 * <p>Sends a notification to the subscribers for a given MBean.</p> |
|
182 * |
|
183 * <p>For each listener subscribed with an {@code ObjectName} that either |
|
184 * is equal to {@code emitterName} or is a pattern that matches {@code |
|
185 * emitterName}, if the associated filter accepts the notification then it |
|
186 * is forwarded to the listener.</p> |
|
187 * |
|
188 * @param emitterName The name of the MBean emitting the notification. |
|
189 * @param n The notification being sent by the MBean called |
|
190 * {@code emitterName}. |
|
191 * |
|
192 * @throws IllegalArgumentException If the emitterName of the |
|
193 * notification is null or is an {@code ObjectName} pattern. |
|
194 */ |
|
195 public void publish(ObjectName emitterName, Notification n) { |
|
196 if (logger.traceOn()) |
|
197 logger.trace("publish", "" + emitterName); |
|
198 |
|
199 if (n == null) |
|
200 throw new IllegalArgumentException("Null notification"); |
|
201 |
|
202 if (emitterName == null) { |
|
203 throw new IllegalArgumentException( |
|
204 "Null emitter name"); |
|
205 } else if (emitterName.isPattern()) { |
|
206 throw new IllegalArgumentException( |
|
207 "The emitter must not be an ObjectName pattern"); |
|
208 } |
|
209 |
|
210 final List<ListenerInfo> listeners = new ArrayList<ListenerInfo>(); |
|
211 |
|
212 // If there are listeners for this exact name, add them. |
|
213 synchronized (exactSubscriptionMap) { |
|
214 List<ListenerInfo> exactListeners = |
|
215 exactSubscriptionMap.get(emitterName); |
|
216 if (exactListeners != null) |
|
217 listeners.addAll(exactListeners); |
|
218 } |
|
219 |
|
220 // Loop over subscription patterns, and add all listeners for each |
|
221 // one that matches the emitterName name. |
|
222 synchronized (patternSubscriptionMap) { |
|
223 for (ObjectName on : patternSubscriptionMap.keySet()) { |
|
224 if (on.apply(emitterName)) |
|
225 listeners.addAll(patternSubscriptionMap.get(on)); |
|
226 } |
|
227 } |
|
228 |
|
229 // Send the notification to all the listeners we found. |
|
230 sendNotif(listeners, n); |
|
231 } |
|
232 |
|
233 /** |
|
234 * <p>Returns a {@link NotificationEmitter} object which can be used to |
|
235 * subscribe or unsubscribe for notifications with the named |
|
236 * mbean. The returned object implements {@link |
|
237 * NotificationEmitter#addNotificationListener |
|
238 * addNotificationListener(listener, filter, handback)} as |
|
239 * {@link #subscribe this.subscribe(name, listener, filter, handback)} |
|
240 * and the two {@code removeNotificationListener} methods from {@link |
|
241 * NotificationEmitter} as the corresponding {@code unsubscribe} methods |
|
242 * from this class.</p> |
|
243 * |
|
244 * @param name The name of the MBean whose notifications are being |
|
245 * subscribed, or unsuscribed. |
|
246 * |
|
247 * @return A {@link NotificationEmitter} |
|
248 * that can be used to subscribe or unsubscribe for |
|
249 * notifications emitted by the named MBean, or {@code null} if |
|
250 * the MBean does not emit notifications and should not |
|
251 * be considered as a {@code NotificationBroadcaster}. This class |
|
252 * never returns null but a subclass is allowed to. |
|
253 * |
|
254 * @throws InstanceNotFoundException if {@code name} does not exist. |
|
255 * This implementation never throws {@code InstanceNotFoundException} but |
|
256 * a subclass is allowed to override this method to do so. |
|
257 */ |
|
258 public NotificationEmitter |
|
259 getNotificationEmitterFor(final ObjectName name) |
|
260 throws InstanceNotFoundException { |
|
261 final NotificationEmitter emitter = new NotificationEmitter() { |
|
262 public void addNotificationListener(NotificationListener listener, |
|
263 NotificationFilter filter, Object handback) |
|
264 throws IllegalArgumentException { |
|
265 subscribe(name, listener, filter, handback); |
|
266 } |
|
267 |
|
268 public void removeNotificationListener( |
|
269 NotificationListener listener) |
|
270 throws ListenerNotFoundException { |
|
271 unsubscribe(name, listener); |
|
272 } |
|
273 |
|
274 public void removeNotificationListener(NotificationListener listener, |
|
275 NotificationFilter filter, |
|
276 Object handback) |
|
277 throws ListenerNotFoundException { |
|
278 unsubscribe(name, listener, filter, handback); |
|
279 } |
|
280 |
|
281 public MBeanNotificationInfo[] getNotificationInfo() { |
|
282 // Never called. |
|
283 return null; |
|
284 } |
|
285 }; |
|
286 return emitter; |
|
287 } |
|
288 |
|
289 // --------------------------------- |
|
290 // private stuff |
|
291 // --------------------------------- |
|
292 |
|
293 private static class ListenerInfo { |
|
294 public final NotificationListener listener; |
|
295 public final NotificationFilter filter; |
|
296 public final Object handback; |
|
297 |
|
298 public ListenerInfo(NotificationListener listener, |
|
299 NotificationFilter filter, |
|
300 Object handback) { |
|
301 |
|
302 if (listener == null) { |
|
303 throw new IllegalArgumentException("Null listener."); |
|
304 } |
|
305 |
|
306 this.listener = listener; |
|
307 this.filter = filter; |
|
308 this.handback = handback; |
|
309 } |
|
310 |
|
311 /* Two ListenerInfo instances are equal if they have the same |
|
312 * NotificationListener. This means that we can use List.remove |
|
313 * to implement the two-argument removeNotificationListener. |
|
314 */ |
|
315 @Override |
|
316 public boolean equals(Object o) { |
|
317 if (o == this) { |
|
318 return true; |
|
319 } |
|
320 |
|
321 if (!(o instanceof ListenerInfo)) { |
|
322 return false; |
|
323 } |
|
324 |
|
325 return listener.equals(((ListenerInfo)o).listener); |
|
326 } |
|
327 |
|
328 /* Method that compares all four fields, appropriate for the |
|
329 * four-argument removeNotificationListener. |
|
330 */ |
|
331 boolean equals( |
|
332 NotificationListener listener, |
|
333 NotificationFilter filter, |
|
334 Object handback) { |
|
335 return (this.listener == listener && same(this.filter, filter) |
|
336 && same(this.handback, handback)); |
|
337 } |
|
338 |
|
339 private static boolean same(Object x, Object y) { |
|
340 if (x == y) |
|
341 return true; |
|
342 if (x == null) |
|
343 return false; |
|
344 return x.equals(y); |
|
345 } |
|
346 |
|
347 @Override |
|
348 public int hashCode() { |
|
349 return listener.hashCode(); |
|
350 } |
|
351 } |
|
352 |
|
353 private static void sendNotif(List<ListenerInfo> listeners, Notification n) { |
|
354 for (ListenerInfo li : listeners) { |
|
355 if (li.filter == null || |
|
356 li.filter.isNotificationEnabled(n)) { |
|
357 try { |
|
358 li.listener.handleNotification(n, li.handback); |
|
359 } catch (Exception e) { |
|
360 logger.trace("sendNotif", "handleNotification", e); |
|
361 } |
|
362 } |
|
363 } |
|
364 } |
|
365 |
|
366 // --------------------------------- |
|
367 // private variables |
|
368 // --------------------------------- |
|
369 |
|
370 private final Map<ObjectName, List<ListenerInfo>> exactSubscriptionMap = |
|
371 new HashMap<ObjectName, List<ListenerInfo>>(); |
|
372 private final Map<ObjectName, List<ListenerInfo>> patternSubscriptionMap = |
|
373 new HashMap<ObjectName, List<ListenerInfo>>(); |
|
374 |
|
375 // trace issue |
|
376 private static final ClassLogger logger = |
|
377 new ClassLogger("javax.management.event", "EventManager"); |
|
378 } |