|
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. |
|
8 * |
|
9 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
12 * version 2 for more details (a copy is included in the LICENSE file that |
|
13 * accompanied this code). |
|
14 * |
|
15 * You should have received a copy of the GNU General Public License version |
|
16 * 2 along with this work; if not, write to the Free Software Foundation, |
|
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
18 * |
|
19 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
|
20 * CA 95054 USA or visit www.sun.com if you need additional information or |
|
21 * have any questions. |
|
22 */ |
|
23 |
|
24 /* |
|
25 * @test VirtualMBeanTest.java |
|
26 * @bug 5108776 |
|
27 * @summary Test that Virtual MBeans can be implemented and emit notifs. |
|
28 * @author Eamonn McManus |
|
29 */ |
|
30 |
|
31 import java.io.PrintWriter; |
|
32 import java.io.StringWriter; |
|
33 import java.util.Arrays; |
|
34 import java.util.Collections; |
|
35 import java.util.List; |
|
36 import java.util.Map; |
|
37 import java.util.Set; |
|
38 import java.util.TreeMap; |
|
39 import java.util.TreeSet; |
|
40 import java.util.concurrent.ArrayBlockingQueue; |
|
41 import java.util.concurrent.BlockingQueue; |
|
42 import java.util.concurrent.TimeUnit; |
|
43 import javax.management.DynamicMBean; |
|
44 import javax.management.InstanceNotFoundException; |
|
45 import javax.management.MBeanInfo; |
|
46 import javax.management.MBeanServer; |
|
47 import javax.management.MBeanServerFactory; |
|
48 import javax.management.MalformedObjectNameException; |
|
49 import javax.management.Notification; |
|
50 import javax.management.NotificationBroadcaster; |
|
51 import javax.management.NotificationBroadcasterSupport; |
|
52 import javax.management.NotificationEmitter; |
|
53 import javax.management.NotificationListener; |
|
54 import javax.management.ObjectName; |
|
55 import javax.management.RuntimeOperationsException; |
|
56 import javax.management.SendNotification; |
|
57 import javax.management.StandardEmitterMBean; |
|
58 import javax.management.StandardMBean; |
|
59 import javax.management.namespace.JMXNamespace; |
|
60 import javax.management.namespace.JMXNamespaces; |
|
61 import javax.management.namespace.VirtualEventManager; |
|
62 import javax.management.namespace.MBeanServerSupport; |
|
63 import javax.management.timer.TimerMBean; |
|
64 |
|
65 // In this test, we check that the two main use case types for |
|
66 // MBeanServerSupport work correctly: |
|
67 // (1) as a special-purpose implementation of MBeanServer for a fixed number |
|
68 // of MBeans (e.g. for QueryNotificationFilter) |
|
69 // (2) as an MBeanServer supporting Virtual MBeans. |
|
70 // In each case we are particularly interested in the notification behaviour. |
|
71 // We check that the behaviour is correct when calling addNotificationListener |
|
72 // (a) for an MBean that does not exist; (b) for an MBean that does exist but |
|
73 // is not a NotificationEmitter; and (c) for an MBean that exists and is |
|
74 // a NotificationEmitter. We also check the degenerate and usual case |
|
75 // where the MBeanServerSupport subclass does not support notifications |
|
76 // at all. |
|
77 // |
|
78 // Each subclass will have an MBean called test:type=NotEmitter that |
|
79 // does not support addNotificationListener. If it also has MBeans called |
|
80 // test:type=Emitter,* then they are expected to support addNL. No subclass |
|
81 // will have any other MBeans, so in particular no subclass will have |
|
82 // test:type=Nonexistent. |
|
83 // |
|
84 public class VirtualMBeanTest { |
|
85 static final ObjectName |
|
86 nonExistentName, notEmitterName, emitterName1, emitterName2; |
|
87 static { |
|
88 try { |
|
89 nonExistentName = new ObjectName("test:type=NonExistent"); |
|
90 notEmitterName = new ObjectName("test:type=NotEmitter"); |
|
91 emitterName1 = new ObjectName("test:type=Emitter,id=1"); |
|
92 emitterName2 = new ObjectName("test:type=Emitter,id=2"); |
|
93 } catch (MalformedObjectNameException e) { |
|
94 throw new AssertionError(e); |
|
95 } |
|
96 } |
|
97 |
|
98 static final StandardMBean.Options wrappedVisible = new StandardMBean.Options(); |
|
99 static { |
|
100 wrappedVisible.setWrappedObjectVisible(true); |
|
101 } |
|
102 |
|
103 public static interface NothingMBean {} |
|
104 public static class Nothing implements NothingMBean {} |
|
105 public static class NothingNBS extends NotificationBroadcasterSupport |
|
106 implements NothingMBean {} |
|
107 |
|
108 // Class that has hardwired MBeans test:type=NotEmitter, |
|
109 // test:type=Broadcaster, and test:type=Emitter. |
|
110 private static class HardwiredMBS extends MBeanServerSupport |
|
111 implements SendNotification { |
|
112 private final DynamicMBean notEmitter = |
|
113 new StandardMBean(new Nothing(), NothingMBean.class, wrappedVisible); |
|
114 private final StandardEmitterMBean emitter1, emitter2; |
|
115 { |
|
116 NothingNBS nnbs1 = new NothingNBS(); |
|
117 emitter1 = new StandardEmitterMBean( |
|
118 nnbs1, NothingMBean.class, wrappedVisible, nnbs1); |
|
119 NothingNBS nnbs2 = new NothingNBS(); |
|
120 emitter2 = new StandardEmitterMBean( |
|
121 nnbs2, NothingMBean.class, wrappedVisible, nnbs2); |
|
122 } |
|
123 |
|
124 private final Map<ObjectName, DynamicMBean> map = |
|
125 new TreeMap<ObjectName, DynamicMBean>(); |
|
126 { |
|
127 map.put(notEmitterName, notEmitter); |
|
128 map.put(emitterName1, emitter1); |
|
129 map.put(emitterName2, emitter2); |
|
130 } |
|
131 |
|
132 |
|
133 @Override |
|
134 public DynamicMBean getDynamicMBeanFor(ObjectName name) |
|
135 throws InstanceNotFoundException { |
|
136 DynamicMBean mbean = map.get(name); |
|
137 if (mbean != null) |
|
138 return mbean; |
|
139 else |
|
140 throw new InstanceNotFoundException(name); |
|
141 } |
|
142 |
|
143 @Override |
|
144 protected Set<ObjectName> getNames() { |
|
145 return map.keySet(); |
|
146 } |
|
147 |
|
148 @Override |
|
149 public String toString() { |
|
150 return "Hardwired MBeanServerSupport"; |
|
151 } |
|
152 |
|
153 public void sendNotification(Notification notification) { |
|
154 emitter1.sendNotification(notification); |
|
155 emitter2.sendNotification(notification); |
|
156 } |
|
157 } |
|
158 |
|
159 // Class that has the notEmitter MBean but not either of the others, so does |
|
160 // not support listeners. |
|
161 private static class VirtualMBSWithoutListeners |
|
162 extends MBeanServerSupport { |
|
163 @Override |
|
164 public DynamicMBean getDynamicMBeanFor(ObjectName name) |
|
165 throws InstanceNotFoundException { |
|
166 if (name.equals(notEmitterName)) { |
|
167 return new StandardMBean( |
|
168 new Nothing(), NothingMBean.class, wrappedVisible); |
|
169 } else |
|
170 throw new InstanceNotFoundException(name); |
|
171 } |
|
172 |
|
173 @Override |
|
174 protected Set<ObjectName> getNames() { |
|
175 return Collections.singleton(notEmitterName); |
|
176 } |
|
177 |
|
178 @Override |
|
179 public String toString() { |
|
180 return "Virtual MBeanServerSupport without listener support"; |
|
181 } |
|
182 } |
|
183 |
|
184 // Class that has the notEmitter and emitter MBeans as Virtual MBeans, using |
|
185 // VirtualEventManager to handle listeners for the emitter MBean. We |
|
186 // implement the broadcaster MBean (which is a NotificationBroadcaster but |
|
187 // not a NotificationEmitter) even though it's very hard to imagine a real |
|
188 // use case where that would happen. |
|
189 private static class VirtualMBSWithListeners |
|
190 extends MBeanServerSupport implements SendNotification { |
|
191 private final VirtualEventManager vem = new VirtualEventManager(); |
|
192 |
|
193 private static final List<ObjectName> names = |
|
194 Arrays.asList(notEmitterName, emitterName1, emitterName2); |
|
195 |
|
196 @Override |
|
197 public DynamicMBean getDynamicMBeanFor(ObjectName name) |
|
198 throws InstanceNotFoundException { |
|
199 if (names.contains(name)) { |
|
200 return new StandardMBean( |
|
201 new Nothing(), NothingMBean.class, wrappedVisible); |
|
202 } else |
|
203 throw new InstanceNotFoundException(name); |
|
204 } |
|
205 |
|
206 @Override |
|
207 public NotificationEmitter getNotificationEmitterFor( |
|
208 ObjectName name) throws InstanceNotFoundException { |
|
209 if (name.equals(emitterName1) || name.equals(emitterName2)) |
|
210 return vem.getNotificationEmitterFor(name); |
|
211 else if (name.equals(notEmitterName)) |
|
212 return null; |
|
213 else |
|
214 throw new InstanceNotFoundException(name); |
|
215 } |
|
216 |
|
217 @Override |
|
218 protected Set<ObjectName> getNames() { |
|
219 return new TreeSet<ObjectName>(Arrays.asList(notEmitterName, emitterName2)); |
|
220 } |
|
221 |
|
222 @Override |
|
223 public String toString() { |
|
224 return "Virtual MBeanServerSupport with listener support"; |
|
225 } |
|
226 |
|
227 public void sendNotification(Notification notification) { |
|
228 vem.publish(emitterName1, notification); |
|
229 vem.publish(emitterName2, notification); |
|
230 } |
|
231 } |
|
232 |
|
233 private static final MBeanServer[] vmbsss = { |
|
234 new HardwiredMBS(), |
|
235 new VirtualMBSWithoutListeners(), |
|
236 new VirtualMBSWithListeners(), |
|
237 }; |
|
238 |
|
239 public static void main(String[] args) throws Exception { |
|
240 Exception lastEx = null; |
|
241 for (MBeanServer vmbs : vmbsss) { |
|
242 String testName = "\"" + vmbs + "\""; |
|
243 System.out.println("===Test " + testName + "==="); |
|
244 try { |
|
245 test(vmbs); |
|
246 } catch (Exception e) { |
|
247 System.out.println( |
|
248 "===Test " + testName + " failed with exception " + e); |
|
249 StringWriter sw = new StringWriter(); |
|
250 PrintWriter pw = new PrintWriter(sw); |
|
251 e.printStackTrace(pw); |
|
252 pw.flush(); |
|
253 String es = sw.toString(); |
|
254 System.out.println("......" + es.replace("\n", "\n......")); |
|
255 lastEx = e; |
|
256 } |
|
257 } |
|
258 if (lastEx != null) |
|
259 throw lastEx; |
|
260 System.out.println("TEST PASSED"); |
|
261 } |
|
262 |
|
263 private static class NothingListener implements NotificationListener { |
|
264 public void handleNotification(Notification notification, |
|
265 Object handback) { |
|
266 throw new UnsupportedOperationException("Not supported yet."); |
|
267 } |
|
268 } |
|
269 |
|
270 private static class QueueListener implements NotificationListener { |
|
271 final BlockingQueue<Notification> queue = |
|
272 new ArrayBlockingQueue<Notification>(10); |
|
273 |
|
274 public void handleNotification(Notification notification, |
|
275 Object handback) { |
|
276 queue.add(notification); |
|
277 } |
|
278 } |
|
279 |
|
280 private static void test(MBeanServer vmbs) throws Exception { |
|
281 MBeanServer mmbs = MBeanServerFactory.newMBeanServer(); |
|
282 ObjectName namespaceName = new ObjectName("test//:type=JMXNamespace"); |
|
283 JMXNamespace namespace = new JMXNamespace(vmbs); |
|
284 mmbs.registerMBean(namespace, namespaceName); |
|
285 MBeanServer mbs = JMXNamespaces.narrowToNamespace(mmbs, "test"); |
|
286 |
|
287 Set<ObjectName> names = mbs.queryNames(null, null); |
|
288 //names.remove(new ObjectName(":type=JMXNamespace")); |
|
289 |
|
290 // Make sure that notEmitterName exists according to query... |
|
291 System.out.println("Checking query"); |
|
292 if (!names.contains(notEmitterName)) |
|
293 throw new Exception("Bad query result: " + names); |
|
294 |
|
295 // ...and according to getMBeanInfo |
|
296 System.out.println("Checking getMBeanInfo(" + notEmitterName + ")"); |
|
297 MBeanInfo mbi = mbs.getMBeanInfo(notEmitterName); |
|
298 if (mbi.getNotifications().length > 0) |
|
299 throw new Exception("notEmitter has NotificationInfo"); |
|
300 |
|
301 // Make sure we get the right exception for getMBeanInfo on a |
|
302 // non-existent MBean |
|
303 System.out.println("Checking getMBeanInfo on a non-existent MBean"); |
|
304 try { |
|
305 mbi = mbs.getMBeanInfo(nonExistentName); |
|
306 throw new Exception("getMBI succeeded but should not have"); |
|
307 } catch (InstanceNotFoundException e) { |
|
308 } |
|
309 |
|
310 // Make sure we get the right exception for addNotificationListener on a |
|
311 // non-existent MBean |
|
312 System.out.println( |
|
313 "Checking addNotificationListener on a non-existent MBean"); |
|
314 try { |
|
315 mbs.addNotificationListener( |
|
316 nonExistentName, new NothingListener(), null, null); |
|
317 throw new Exception("addNL succeeded but should not have"); |
|
318 } catch (InstanceNotFoundException e) { |
|
319 } |
|
320 |
|
321 // Make sure we get the right exception for isInstanceOf on a |
|
322 // non-existent MBean |
|
323 System.out.println( |
|
324 "Checking isInstanceOf on a non-existent MBean"); |
|
325 for (Class<?> c : new Class<?>[] { |
|
326 Object.class, NotificationBroadcaster.class, NotificationEmitter.class, |
|
327 }) { |
|
328 try { |
|
329 boolean is = mbs.isInstanceOf(nonExistentName, c.getName()); |
|
330 throw new Exception( |
|
331 "isInstanceOf " + c.getName() + |
|
332 " succeeded but should not have"); |
|
333 } catch (InstanceNotFoundException e) { |
|
334 } |
|
335 } |
|
336 |
|
337 // Make sure isInstanceOf works correctly for classes without special |
|
338 // treatment |
|
339 System.out.println( |
|
340 "Checking isInstanceOf on normal classes"); |
|
341 for (ObjectName name : names) { |
|
342 boolean isNothing = mbs.isInstanceOf(name, NothingMBean.class.getName()); |
|
343 if (!isNothing) { |
|
344 throw new Exception("isInstanceOf " + NothingMBean.class.getName() + |
|
345 " returned false, should be true"); |
|
346 } |
|
347 boolean isTimer = mbs.isInstanceOf(name, TimerMBean.class.getName()); |
|
348 if (isTimer) { |
|
349 throw new Exception("isInstanceOf " + TimerMBean.class.getName() + |
|
350 " returned true, should be false"); |
|
351 } |
|
352 } |
|
353 |
|
354 // Make sure that addNL on notEmitterName gets the right exception |
|
355 System.out.println("Checking addNL on non-broadcaster"); |
|
356 try { |
|
357 mbs.addNotificationListener( |
|
358 notEmitterName, new NothingListener(), null, null); |
|
359 throw new Exception("addNL succeeded but should not have"); |
|
360 } catch (RuntimeOperationsException e) { |
|
361 if (!(e.getCause() instanceof IllegalArgumentException)) |
|
362 throw new Exception("Wrong exception from addNL", e); |
|
363 } |
|
364 |
|
365 if (!(vmbs instanceof SendNotification)) { |
|
366 System.out.println("Not testing notifications for this implementation"); |
|
367 return; |
|
368 } |
|
369 |
|
370 QueueListener qListener = new QueueListener(); |
|
371 |
|
372 System.out.println("Testing addNL on emitters"); |
|
373 mbs.addNotificationListener(emitterName1, qListener, null, null); |
|
374 mbs.addNotificationListener(emitterName2, qListener, null, null); |
|
375 |
|
376 System.out.println("Testing that listeners work"); |
|
377 Notification notif = new Notification("notif.type", "source", 0L); |
|
378 |
|
379 ((SendNotification) vmbs).sendNotification(notif); |
|
380 testListeners(qListener, "notif.type", 2); |
|
381 |
|
382 System.out.println("Testing 2-arg removeNL on emitter1"); |
|
383 mbs.removeNotificationListener(emitterName1, qListener); |
|
384 |
|
385 ((SendNotification) vmbs).sendNotification(notif); |
|
386 testListeners(qListener, "notif.type", 1); |
|
387 |
|
388 System.out.println("Testing 4-arg removeNL on emitter2"); |
|
389 mbs.removeNotificationListener(emitterName2, qListener, null, null); |
|
390 |
|
391 ((SendNotification) vmbs).sendNotification(notif); |
|
392 testListeners(qListener, "notif.type", 0); |
|
393 } |
|
394 |
|
395 private static void testListeners( |
|
396 QueueListener qListener, String expectedNotifType, int expectedNotifs) |
|
397 throws Exception { |
|
398 for (int i = 1; i <= expectedNotifs; i++) { |
|
399 Notification rNotif = qListener.queue.poll(1, TimeUnit.SECONDS); |
|
400 if (rNotif == null) |
|
401 throw new Exception("Notification " + i + " never arrived"); |
|
402 if (!rNotif.getType().equals(expectedNotifType)) |
|
403 throw new Exception("Wrong type notif: " + rNotif.getType()); |
|
404 } |
|
405 Notification xNotif = qListener.queue.poll(10, TimeUnit.MILLISECONDS); |
|
406 if (xNotif != null) |
|
407 throw new Exception("Extra notif: " + xNotif); |
|
408 } |
|
409 } |