1 /* |
|
2 * Copyright 2007-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 RemoteContextTest.java |
|
26 * @bug 5072267 |
|
27 * @summary Test client contexts with namespaces. |
|
28 * @author Eamonn McManus, Daniel Fuchs |
|
29 */ |
|
30 |
|
31 import java.lang.management.ManagementFactory; |
|
32 import java.lang.reflect.InvocationHandler; |
|
33 import java.lang.reflect.InvocationTargetException; |
|
34 import java.lang.reflect.Method; |
|
35 import java.lang.reflect.Proxy; |
|
36 import java.net.URLEncoder; |
|
37 import java.util.Arrays; |
|
38 import java.util.Collections; |
|
39 import java.util.HashMap; |
|
40 import java.util.HashSet; |
|
41 import java.util.LinkedList; |
|
42 import java.util.Map; |
|
43 import java.util.Queue; |
|
44 import java.util.Set; |
|
45 import java.util.concurrent.Callable; |
|
46 import javax.management.Attribute; |
|
47 import javax.management.AttributeList; |
|
48 import javax.management.ClientContext; |
|
49 import javax.management.DynamicMBean; |
|
50 import javax.management.JMX; |
|
51 import javax.management.ListenerNotFoundException; |
|
52 import javax.management.MBeanNotificationInfo; |
|
53 import javax.management.MBeanRegistration; |
|
54 import javax.management.MBeanServer; |
|
55 import javax.management.MBeanServerConnection; |
|
56 import javax.management.MBeanServerDelegate; |
|
57 import javax.management.Notification; |
|
58 import javax.management.NotificationBroadcasterSupport; |
|
59 import javax.management.NotificationFilter; |
|
60 import javax.management.NotificationListener; |
|
61 import javax.management.ObjectInstance; |
|
62 import javax.management.ObjectName; |
|
63 import javax.management.StandardMBean; |
|
64 import javax.management.loading.MLet; |
|
65 import javax.management.namespace.JMXNamespaces; |
|
66 import javax.management.namespace.JMXRemoteNamespace; |
|
67 import javax.management.namespace.JMXNamespace; |
|
68 |
|
69 import static java.util.Collections.singletonMap; |
|
70 import javax.management.MBeanServerFactory; |
|
71 import javax.management.remote.JMXConnectorServer; |
|
72 import javax.management.remote.JMXConnectorServerFactory; |
|
73 import javax.management.remote.JMXServiceURL; |
|
74 |
|
75 public class RemoteContextTest { |
|
76 private static String failure; |
|
77 |
|
78 public static interface ShowContextMBean { |
|
79 public Map<String, String> getContext(); |
|
80 public Map<String, String> getCreationContext(); |
|
81 public Set<String> getCalledOps(); |
|
82 public String getThing(); |
|
83 public void setThing(String x); |
|
84 public int add(int x, int y); |
|
85 } |
|
86 |
|
87 public static class ShowContext |
|
88 extends NotificationBroadcasterSupport |
|
89 implements ShowContextMBean, MBeanRegistration { |
|
90 private final Map<String, String> creationContext; |
|
91 private final Set<String> calledOps = new HashSet<String>(); |
|
92 |
|
93 public ShowContext() { |
|
94 creationContext = getContext(); |
|
95 } |
|
96 |
|
97 public Map<String, String> getContext() { |
|
98 return ClientContext.getContext(); |
|
99 } |
|
100 |
|
101 public Map<String, String> getCreationContext() { |
|
102 return creationContext; |
|
103 } |
|
104 |
|
105 public Set<String> getCalledOps() { |
|
106 return calledOps; |
|
107 } |
|
108 |
|
109 public String getThing() { |
|
110 return "x"; |
|
111 } |
|
112 |
|
113 public void setThing(String x) { |
|
114 } |
|
115 |
|
116 public int add(int x, int y) { |
|
117 return x + y; |
|
118 } |
|
119 |
|
120 public ObjectName preRegister(MBeanServer server, ObjectName name) { |
|
121 assertEquals(creationContext, getContext()); |
|
122 calledOps.add("preRegister"); |
|
123 return name; |
|
124 } |
|
125 |
|
126 public void postRegister(Boolean registrationDone) { |
|
127 assertEquals(creationContext, getContext()); |
|
128 calledOps.add("postRegister"); |
|
129 } |
|
130 |
|
131 // The condition checked here is not guaranteed universally true, |
|
132 // but is true every time we unregister an instance of this MBean |
|
133 // in this test. |
|
134 public void preDeregister() throws Exception { |
|
135 assertEquals(creationContext, getContext()); |
|
136 } |
|
137 |
|
138 public void postDeregister() { |
|
139 assertEquals(creationContext, getContext()); |
|
140 } |
|
141 |
|
142 // Same remark as for preDeregister |
|
143 @Override |
|
144 public MBeanNotificationInfo[] getNotificationInfo() { |
|
145 calledOps.add("getNotificationInfo"); |
|
146 return super.getNotificationInfo(); |
|
147 } |
|
148 |
|
149 @Override |
|
150 public void addNotificationListener( |
|
151 NotificationListener listener, NotificationFilter filter, Object handback) { |
|
152 calledOps.add("addNotificationListener"); |
|
153 super.addNotificationListener(listener, filter, handback); |
|
154 } |
|
155 |
|
156 @Override |
|
157 public void removeNotificationListener( |
|
158 NotificationListener listener) |
|
159 throws ListenerNotFoundException { |
|
160 calledOps.add("removeNL1"); |
|
161 super.removeNotificationListener(listener); |
|
162 } |
|
163 |
|
164 @Override |
|
165 public void removeNotificationListener( |
|
166 NotificationListener listener, NotificationFilter filter, Object handback) |
|
167 throws ListenerNotFoundException { |
|
168 calledOps.add("removeNL3"); |
|
169 super.removeNotificationListener(listener, filter, handback); |
|
170 } |
|
171 } |
|
172 |
|
173 private static class LogRecord { |
|
174 final String op; |
|
175 final Object[] params; |
|
176 final Map<String, String> context; |
|
177 LogRecord(String op, Object[] params, Map<String, String> context) { |
|
178 this.op = op; |
|
179 this.params = params; |
|
180 this.context = context; |
|
181 } |
|
182 |
|
183 @Override |
|
184 public String toString() { |
|
185 return op + Arrays.deepToString(params) + " " + context; |
|
186 } |
|
187 } |
|
188 |
|
189 private static class LogIH implements InvocationHandler { |
|
190 private final Object wrapped; |
|
191 Queue<LogRecord> log = new LinkedList<LogRecord>(); |
|
192 |
|
193 LogIH(Object wrapped) { |
|
194 this.wrapped = wrapped; |
|
195 } |
|
196 |
|
197 public Object invoke(Object proxy, Method method, Object[] args) |
|
198 throws Throwable { |
|
199 if (method.getDeclaringClass() != Object.class) { |
|
200 LogRecord lr = |
|
201 new LogRecord( |
|
202 method.getName(), args, ClientContext.getContext()); |
|
203 log.add(lr); |
|
204 } |
|
205 try { |
|
206 return method.invoke(wrapped, args); |
|
207 } catch (InvocationTargetException e) { |
|
208 throw e.getCause(); |
|
209 } |
|
210 } |
|
211 } |
|
212 |
|
213 private static <T> T newSnoop(Class<T> wrappedClass, LogIH logIH) { |
|
214 return wrappedClass.cast(Proxy.newProxyInstance( |
|
215 wrappedClass.getClassLoader(), |
|
216 new Class<?>[] {wrappedClass}, |
|
217 logIH)); |
|
218 } |
|
219 |
|
220 public static void main(String[] args) throws Exception { |
|
221 final String subnamespace = "sub"; |
|
222 final ObjectName locname = new ObjectName("a:b=c"); |
|
223 final ObjectName name = JMXNamespaces.insertPath(subnamespace,locname); |
|
224 final MBeanServer mbs = ClientContext.newContextForwarder( |
|
225 ManagementFactory.getPlatformMBeanServer(), null); |
|
226 final MBeanServer sub = ClientContext.newContextForwarder( |
|
227 MBeanServerFactory.newMBeanServer(), null); |
|
228 final JMXServiceURL anonym = new JMXServiceURL("rmi",null,0); |
|
229 final Map<String, Object> env = Collections.emptyMap(); |
|
230 final Map<String, String> emptyContext = Collections.emptyMap(); |
|
231 final JMXConnectorServer srv = |
|
232 JMXConnectorServerFactory.newJMXConnectorServer(anonym,env,sub); |
|
233 sub.registerMBean(new ShowContext(), locname); |
|
234 |
|
235 srv.start(); |
|
236 |
|
237 try { |
|
238 JMXRemoteNamespace subns = JMXRemoteNamespace. |
|
239 newJMXRemoteNamespace(srv.getAddress(),null); |
|
240 mbs.registerMBean(subns, JMXNamespaces.getNamespaceObjectName("sub")); |
|
241 mbs.invoke(JMXNamespaces.getNamespaceObjectName("sub"), |
|
242 "connect", null,null); |
|
243 final ShowContextMBean show = |
|
244 JMX.newMBeanProxy(mbs, name, ShowContextMBean.class); |
|
245 |
|
246 assertEquals(emptyContext, show.getContext()); |
|
247 ClientContext.doWithContext(singletonMap("foo", "bar"), new Callable<Void>() { |
|
248 public Void call() { |
|
249 assertEquals(singletonMap("foo", "bar"), show.getContext()); |
|
250 return null; |
|
251 } |
|
252 }); |
|
253 assertEquals(emptyContext, show.getContext()); |
|
254 String got = ClientContext.doWithContext( |
|
255 singletonMap("foo", "baz"), new Callable<String>() { |
|
256 public String call() { |
|
257 return ClientContext.getContext().get("foo"); |
|
258 } |
|
259 }); |
|
260 assertEquals("baz", got); |
|
261 |
|
262 Map<String, String> combined = ClientContext.doWithContext( |
|
263 singletonMap("foo", "baz"), new Callable<Map<String, String>>() { |
|
264 public Map<String, String> call() throws Exception { |
|
265 return ClientContext.doWithContext( |
|
266 singletonMap("fred", "jim"), |
|
267 new Callable<Map<String, String>>() { |
|
268 public Map<String, String> call() { |
|
269 return ClientContext.getContext(); |
|
270 } |
|
271 }); |
|
272 } |
|
273 }); |
|
274 assertEquals(singletonMap("fred", "jim"), combined); |
|
275 |
|
276 final String ugh = "a!?//*=:\"% "; |
|
277 ClientContext.doWithContext(singletonMap(ugh, ugh), new Callable<Void>() { |
|
278 public Void call() { |
|
279 assertEquals(Collections.singletonMap(ugh, ugh), |
|
280 ClientContext.getContext()); |
|
281 return null; |
|
282 } |
|
283 }); |
|
284 |
|
285 // Basic withContext tests |
|
286 |
|
287 LogIH mbsIH = new LogIH(mbs); |
|
288 MBeanServer snoopMBS = newSnoop(MBeanServer.class, mbsIH); |
|
289 MBeanServer ughMBS = ClientContext.withContext(snoopMBS, ugh, ugh); |
|
290 // ughMBS is never referenced but we check that the withContext call |
|
291 // included a call to snoopMBS.isRegistered. |
|
292 String encodedUgh = URLEncoder.encode(ugh, "UTF-8").replace("*", "%2A"); |
|
293 ObjectName expectedName = new ObjectName( |
|
294 ClientContext.NAMESPACE + ObjectName.NAMESPACE_SEPARATOR + |
|
295 encodedUgh + "=" + encodedUgh + |
|
296 ObjectName.NAMESPACE_SEPARATOR + ":" + |
|
297 JMXNamespace.TYPE_ASSIGNMENT); |
|
298 assertCalled(mbsIH, "isRegistered", new Object[] {expectedName}, |
|
299 emptyContext); |
|
300 |
|
301 // Test withDynamicContext |
|
302 |
|
303 MBeanServerConnection dynamicSnoop = |
|
304 ClientContext.withDynamicContext(snoopMBS); |
|
305 assertCalled(mbsIH, "isRegistered", |
|
306 new Object[] { |
|
307 JMXNamespaces.getNamespaceObjectName(ClientContext.NAMESPACE) |
|
308 }, |
|
309 emptyContext); |
|
310 final ShowContextMBean dynamicShow = |
|
311 JMX.newMBeanProxy(dynamicSnoop, name, ShowContextMBean.class); |
|
312 assertEquals(Collections.emptyMap(), dynamicShow.getContext()); |
|
313 assertCalled(mbsIH, "getAttribute", new Object[] {name, "Context"}, |
|
314 emptyContext); |
|
315 |
|
316 Map<String, String> expectedDynamic = |
|
317 Collections.singletonMap("gladstone", "gander"); |
|
318 Map<String, String> dynamic = ClientContext.doWithContext( |
|
319 expectedDynamic, |
|
320 new Callable<Map<String, String>>() { |
|
321 public Map<String, String> call() throws Exception { |
|
322 return dynamicShow.getContext(); |
|
323 } |
|
324 }); |
|
325 assertEquals(expectedDynamic, dynamic); |
|
326 ObjectName expectedDynamicName = new ObjectName( |
|
327 ClientContext.encode(expectedDynamic) + |
|
328 ObjectName.NAMESPACE_SEPARATOR + name); |
|
329 assertCalled(mbsIH, "getAttribute", |
|
330 new Object[] {expectedDynamicName, "Context"}, dynamic); |
|
331 |
|
332 MBeanServer cmbs = ClientContext.withContext( |
|
333 mbs, "mickey", "mouse"); |
|
334 ShowContextMBean cshow = |
|
335 JMX.newMBeanProxy(cmbs, name, ShowContextMBean.class); |
|
336 assertEquals(Collections.singletonMap("mickey", "mouse"), cshow.getContext()); |
|
337 |
|
338 MBeanServer ccmbs = ClientContext.withContext( |
|
339 cmbs, "donald", "duck"); |
|
340 ShowContextMBean ccshow = |
|
341 JMX.newMBeanProxy(ccmbs, name, ShowContextMBean.class); |
|
342 Map<String, String> disney = new HashMap<String, String>(); |
|
343 disney.put("mickey", "mouse"); |
|
344 disney.put("donald", "duck"); |
|
345 assertEquals(disney, ccshow.getContext()); |
|
346 |
|
347 // Test that all MBS ops produce reasonable results |
|
348 |
|
349 ObjectName logger = new ObjectName("a:type=Logger"); |
|
350 DynamicMBean showMBean = |
|
351 new StandardMBean(new ShowContext(), ShowContextMBean.class); |
|
352 LogIH mbeanLogIH = new LogIH(showMBean); |
|
353 DynamicMBean logMBean = newSnoop(DynamicMBean.class, mbeanLogIH); |
|
354 ObjectInstance loggerOI = ccmbs.registerMBean(logMBean, logger); |
|
355 assertEquals(logger, loggerOI.getObjectName()); |
|
356 |
|
357 // We get a getMBeanInfo call to determine the className in the |
|
358 // ObjectInstance to return from registerMBean. |
|
359 assertCalled(mbeanLogIH, "getMBeanInfo", disney); |
|
360 |
|
361 ccmbs.getAttribute(logger, "Thing"); |
|
362 assertCalled(mbeanLogIH, "getAttribute", disney); |
|
363 |
|
364 ccmbs.getAttributes(logger, new String[] {"Thing", "Context"}); |
|
365 assertCalled(mbeanLogIH, "getAttributes", disney); |
|
366 |
|
367 ccmbs.setAttribute(logger, new Attribute("Thing", "bar")); |
|
368 assertCalled(mbeanLogIH, "setAttribute", disney); |
|
369 |
|
370 ccmbs.setAttributes(logger, new AttributeList( |
|
371 Arrays.asList(new Attribute("Thing", "baz")))); |
|
372 assertCalled(mbeanLogIH, "setAttributes", disney); |
|
373 |
|
374 ccmbs.getMBeanInfo(logger); |
|
375 assertCalled(mbeanLogIH, "getMBeanInfo", disney); |
|
376 |
|
377 Set<ObjectName> names = ccmbs.queryNames(null, null); |
|
378 Set<ObjectName> expectedNames = new HashSet<ObjectName>( |
|
379 Collections.singleton(MBeanServerDelegate.DELEGATE_NAME)); |
|
380 expectedNames.removeAll(names); |
|
381 assertEquals(0, expectedNames.size()); |
|
382 |
|
383 Set<ObjectName> nsNames = |
|
384 ccmbs.queryNames(new ObjectName("**?*?//:*"), null); |
|
385 Set<ObjectName> expectedNsNames = new HashSet<ObjectName>( |
|
386 Arrays.asList( |
|
387 new ObjectName(ClientContext.NAMESPACE + |
|
388 ObjectName.NAMESPACE_SEPARATOR + ":" + |
|
389 JMXNamespace.TYPE_ASSIGNMENT))); |
|
390 expectedNsNames.removeAll(nsNames); |
|
391 assertEquals(0, expectedNsNames.size()); |
|
392 |
|
393 Set<ObjectInstance> insts = ccmbs.queryMBeans( |
|
394 MBeanServerDelegate.DELEGATE_NAME, null); |
|
395 assertEquals(1, insts.size()); |
|
396 assertEquals(MBeanServerDelegate.DELEGATE_NAME, |
|
397 insts.iterator().next().getObjectName()); |
|
398 |
|
399 ObjectName createdName = new ObjectName("a:type=Created"); |
|
400 ObjectInstance createdOI = |
|
401 ccmbs.createMBean(ShowContext.class.getName(), createdName); |
|
402 assertEquals(ShowContext.class.getName(), createdOI.getClassName()); |
|
403 assertEquals(createdName, createdOI.getObjectName()); |
|
404 assertEquals(disney, ccmbs.getAttribute(createdName, "CreationContext")); |
|
405 |
|
406 NotificationListener nothingListener = new NotificationListener() { |
|
407 public void handleNotification(Notification n, Object h) {} |
|
408 }; |
|
409 ccmbs.addNotificationListener(createdName, nothingListener, null, null); |
|
410 ccmbs.removeNotificationListener(createdName, nothingListener, null, null); |
|
411 ccmbs.addNotificationListener(createdName, nothingListener, null, null); |
|
412 ccmbs.removeNotificationListener(createdName, nothingListener); |
|
413 Set<String> expectedOps = new HashSet<String>(Arrays.asList( |
|
414 "preRegister", "postRegister", "addNotificationListener", |
|
415 "removeNL1", "removeNL3", "getNotificationInfo")); |
|
416 assertEquals(expectedOps, ccmbs.getAttribute(createdName, "CalledOps")); |
|
417 |
|
418 assertEquals(ShowContext.class.getClassLoader(), |
|
419 ccmbs.getClassLoaderFor(createdName)); |
|
420 |
|
421 assertEquals(true, ccmbs.isRegistered(createdName)); |
|
422 assertEquals(true, ccmbs.isInstanceOf(createdName, |
|
423 ShowContext.class.getName())); |
|
424 assertEquals(false, ccmbs.isInstanceOf(createdName, |
|
425 DynamicMBean.class.getName())); |
|
426 ccmbs.unregisterMBean(createdName); |
|
427 assertEquals(false, ccmbs.isRegistered(createdName)); |
|
428 |
|
429 MLet mlet = new MLet(); |
|
430 ObjectName defaultMLetName = new ObjectName("DefaultDomain:type=MLet"); |
|
431 |
|
432 ccmbs.registerMBean(mlet, defaultMLetName); |
|
433 |
|
434 assertEquals(mlet, ccmbs.getClassLoader(defaultMLetName)); |
|
435 |
|
436 assertEquals(0, mbeanLogIH.log.size()); |
|
437 |
|
438 // Test that contexts still work when we can't combine two encoded contexts. |
|
439 // Here, we wrap cmbs (mickey=mouse) so that ccmbs2 (donald=duck) cannot |
|
440 // see that it already contains a context and therefore cannot combine |
|
441 // into mickey=mouse;donald=duck. We don't actually use the snoop |
|
442 // capabilities of the returned object -- we just want an opaque |
|
443 // MBeanServer wrapper |
|
444 MBeanServer cmbs2 = newSnoop(MBeanServer.class, new LogIH(cmbs)); |
|
445 MBeanServer ccmbs2 = ClientContext.withContext(cmbs2, "donald", "duck"); |
|
446 assertEquals(disney, ccmbs2.getAttribute(name, "Context")); |
|
447 |
|
448 // ADD NEW TESTS HERE ^^^ |
|
449 |
|
450 if (failure != null) |
|
451 throw new Exception(failure); |
|
452 } finally { |
|
453 srv.stop(); |
|
454 } |
|
455 } |
|
456 |
|
457 private static void assertEquals(Object x, Object y) { |
|
458 if (!equal(x, y)) |
|
459 failed("expected " + string(x) + "; got " + string(y)); |
|
460 } |
|
461 |
|
462 private static boolean equal(Object x, Object y) { |
|
463 if (x == y) |
|
464 return true; |
|
465 if (x == null || y == null) |
|
466 return false; |
|
467 if (x.getClass().isArray()) |
|
468 return Arrays.deepEquals(new Object[] {x}, new Object[] {y}); |
|
469 return x.equals(y); |
|
470 } |
|
471 |
|
472 private static String string(Object x) { |
|
473 String s = Arrays.deepToString(new Object[] {x}); |
|
474 return s.substring(1, s.length() - 1); |
|
475 } |
|
476 |
|
477 private static void assertCalled( |
|
478 LogIH logIH, String op, Map<String, String> expectedContext) { |
|
479 assertCalled(logIH, op, null, expectedContext); |
|
480 } |
|
481 |
|
482 private static void assertCalled( |
|
483 LogIH logIH, String op, Object[] params, |
|
484 Map<String, String> expectedContext) { |
|
485 LogRecord lr = logIH.log.remove(); |
|
486 assertEquals(op, lr.op); |
|
487 if (params != null) |
|
488 assertEquals(params, lr.params); |
|
489 assertEquals(expectedContext, lr.context); |
|
490 } |
|
491 |
|
492 private static void failed(String why) { |
|
493 failure = why; |
|
494 new Throwable("FAILED: " + why).printStackTrace(System.out); |
|
495 } |
|
496 } |
|