author | dfuchs |
Thu, 04 Sep 2008 14:46:36 +0200 | |
changeset 1156 | bbc2d15aaf7a |
parent 1004 | 5ba8217eb504 |
child 1247 | b4c26443dee5 |
permissions | -rw-r--r-- |
1004 | 1 |
/* |
2 |
* Copyright 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 com.sun.jmx.remote.util; |
|
27 |
||
1156
bbc2d15aaf7a
5072476: RFE: support cascaded (federated) MBean Servers
dfuchs
parents:
1004
diff
changeset
|
28 |
import com.sun.jmx.defaults.JmxProperties; |
1004 | 29 |
import com.sun.jmx.event.EventClientFactory; |
30 |
||
31 |
import java.lang.reflect.InvocationHandler; |
|
32 |
import java.lang.reflect.InvocationTargetException; |
|
33 |
import java.lang.reflect.Method; |
|
34 |
import java.lang.reflect.Proxy; |
|
35 |
import java.util.Arrays; |
|
36 |
import java.util.concurrent.Callable; |
|
37 |
import java.util.concurrent.TimeUnit; |
|
38 |
import java.util.concurrent.locks.Lock; |
|
39 |
import java.util.concurrent.locks.ReentrantLock; |
|
40 |
import java.util.logging.Level; |
|
41 |
import java.util.logging.Logger; |
|
42 |
||
43 |
import javax.management.MBeanServerConnection; |
|
44 |
import javax.management.NotificationFilter; |
|
45 |
import javax.management.NotificationListener; |
|
46 |
import javax.management.ObjectName; |
|
47 |
import javax.management.event.EventClient; |
|
48 |
import javax.management.event.EventClientDelegate; |
|
1156
bbc2d15aaf7a
5072476: RFE: support cascaded (federated) MBean Servers
dfuchs
parents:
1004
diff
changeset
|
49 |
import javax.management.namespace.JMXNamespaces; |
1004 | 50 |
|
51 |
/** |
|
52 |
* Class EventClientConnection - a {@link Proxy} that wraps an |
|
53 |
* {@link MBeanServerConnection} and an {@link EventClient}. |
|
54 |
* All methods are routed to the underlying {@code MBeanServerConnection}, |
|
55 |
* except add/remove notification listeners which are routed to the |
|
56 |
* {@code EventClient}. |
|
57 |
* The caller only sees an {@code MBeanServerConnection} which uses an |
|
58 |
* {@code EventClient} behind the scenes. |
|
59 |
* |
|
60 |
* @author Sun Microsystems, Inc. |
|
61 |
*/ |
|
62 |
public class EventClientConnection implements InvocationHandler, |
|
63 |
EventClientFactory { |
|
64 |
||
65 |
/** |
|
66 |
* A logger for this class. |
|
67 |
**/ |
|
1156
bbc2d15aaf7a
5072476: RFE: support cascaded (federated) MBean Servers
dfuchs
parents:
1004
diff
changeset
|
68 |
private static final Logger LOG = JmxProperties.NOTIFICATION_LOGGER; |
1004 | 69 |
|
70 |
private static final int NAMESPACE_SEPARATOR_LENGTH = |
|
1156
bbc2d15aaf7a
5072476: RFE: support cascaded (federated) MBean Servers
dfuchs
parents:
1004
diff
changeset
|
71 |
JMXNamespaces.NAMESPACE_SEPARATOR.length(); |
1004 | 72 |
|
73 |
/** |
|
74 |
* Creates a new {@code EventClientConnection}. |
|
75 |
* @param connection The underlying MBeanServerConnection. |
|
76 |
*/ |
|
77 |
public EventClientConnection(MBeanServerConnection connection) { |
|
78 |
this(connection,null); |
|
79 |
} |
|
80 |
||
81 |
/** |
|
82 |
* Creates a new {@code EventClientConnection}. |
|
83 |
* @param connection The underlying MBeanServerConnection. |
|
84 |
* @param eventClientFactory a factory object that will be invoked |
|
85 |
* to create an {@link EventClient} when needed. |
|
86 |
* The {@code EventClient} is created lazily, when it is needed |
|
87 |
* for the first time. If null, a default factory will be used |
|
88 |
* (see {@link #createEventClient}). |
|
89 |
*/ |
|
90 |
public EventClientConnection(MBeanServerConnection connection, |
|
91 |
Callable<EventClient> eventClientFactory) { |
|
92 |
||
93 |
if (connection == null) { |
|
94 |
throw new IllegalArgumentException("Null connection"); |
|
95 |
} |
|
96 |
this.connection = connection; |
|
97 |
if (eventClientFactory == null) { |
|
98 |
eventClientFactory = new Callable<EventClient>() { |
|
99 |
public final EventClient call() throws Exception { |
|
100 |
return createEventClient(EventClientConnection.this.connection); |
|
101 |
} |
|
102 |
}; |
|
103 |
} |
|
104 |
this.eventClientFactory = eventClientFactory; |
|
105 |
this.lock = new ReentrantLock(); |
|
106 |
} |
|
107 |
||
108 |
/** |
|
109 |
* <p>The MBean server connection through which the methods of |
|
110 |
* a proxy using this handler are forwarded.</p> |
|
111 |
* |
|
112 |
* @return the MBean server connection. |
|
113 |
* |
|
114 |
* @since 1.6 |
|
115 |
*/ |
|
116 |
public MBeanServerConnection getMBeanServerConnection() { |
|
117 |
return connection; |
|
118 |
} |
|
119 |
||
120 |
||
121 |
||
122 |
||
123 |
/** |
|
124 |
* Creates a new EventClientConnection proxy instance. |
|
125 |
* |
|
126 |
* @param <T> The underlying {@code MBeanServerConnection} - which should |
|
127 |
* not be using the Event Service itself. |
|
128 |
* @param interfaceClass {@code MBeanServerConnection.class}, or a subclass. |
|
129 |
* @param eventClientFactory a factory used to create the EventClient. |
|
130 |
* If null, a default factory is used (see {@link |
|
131 |
* #createEventClient}). |
|
132 |
* @return the new proxy instance, which will route add/remove notification |
|
133 |
* listener calls through an {@code EventClient}. |
|
134 |
* |
|
135 |
*/ |
|
136 |
private static <T extends MBeanServerConnection> T |
|
137 |
newProxyInstance(T connection, |
|
138 |
Class<T> interfaceClass, Callable<EventClient> eventClientFactory) { |
|
139 |
final InvocationHandler handler = |
|
140 |
new EventClientConnection(connection,eventClientFactory); |
|
141 |
final Class[] interfaces = |
|
142 |
new Class[] {interfaceClass, EventClientFactory.class}; |
|
143 |
||
144 |
Object proxy = |
|
145 |
Proxy.newProxyInstance(interfaceClass.getClassLoader(), |
|
146 |
interfaces, |
|
147 |
handler); |
|
148 |
return interfaceClass.cast(proxy); |
|
149 |
} |
|
150 |
||
151 |
||
152 |
public Object invoke(Object proxy, Method method, Object[] args) |
|
153 |
throws Throwable { |
|
154 |
final String methodName = method.getName(); |
|
155 |
||
156 |
// add/remove notification listener are routed to the EventClient |
|
157 |
if (methodName.equals("addNotificationListener") |
|
158 |
|| methodName.equals("removeNotificationListener")) { |
|
159 |
final Class[] sig = method.getParameterTypes(); |
|
160 |
if (sig.length>1 && |
|
161 |
NotificationListener.class.isAssignableFrom(sig[1])) { |
|
162 |
return invokeBroadcasterMethod(proxy,method,args); |
|
163 |
} |
|
164 |
} |
|
165 |
||
166 |
// subscribe/unsubscribe are also routed to the EventClient. |
|
167 |
final Class clazz = method.getDeclaringClass(); |
|
168 |
if (clazz.equals(EventClientFactory.class)) { |
|
169 |
return invokeEventClientSubscriberMethod(proxy,method,args); |
|
170 |
} |
|
171 |
||
172 |
// local or not: equals, toString, hashCode |
|
173 |
if (shouldDoLocally(proxy, method)) |
|
174 |
return doLocally(proxy, method, args); |
|
175 |
||
176 |
return call(connection,method,args); |
|
177 |
} |
|
178 |
||
179 |
// The purpose of this method is to unwrap InvocationTargetException, |
|
180 |
// in order to avoid throwing UndeclaredThrowableException for |
|
181 |
// declared exceptions. |
|
182 |
// |
|
183 |
// When calling method.invoke(), any exception thrown by the invoked |
|
184 |
// method will be wrapped in InvocationTargetException. If we don't |
|
185 |
// unwrap this exception, the proxy will always throw |
|
186 |
// UndeclaredThrowableException, even for runtime exceptions. |
|
187 |
// |
|
188 |
private Object call(final Object obj, final Method m, |
|
189 |
final Object[] args) |
|
190 |
throws Throwable { |
|
191 |
try { |
|
192 |
return m.invoke(obj,args); |
|
193 |
} catch (InvocationTargetException x) { |
|
194 |
final Throwable xx = x.getTargetException(); |
|
195 |
if (xx == null) throw x; |
|
196 |
else throw xx; |
|
197 |
} |
|
198 |
} |
|
199 |
||
200 |
/** |
|
201 |
* Route add/remove notification listener to the event client. |
|
202 |
**/ |
|
203 |
private Object invokeBroadcasterMethod(Object proxy, Method method, |
|
204 |
Object[] args) throws Exception { |
|
205 |
final String methodName = method.getName(); |
|
206 |
final int nargs = (args == null) ? 0 : args.length; |
|
207 |
||
208 |
if (nargs < 1) { |
|
209 |
final String msg = |
|
210 |
"Bad arg count: " + nargs; |
|
211 |
throw new IllegalArgumentException(msg); |
|
212 |
} |
|
213 |
||
214 |
final ObjectName mbean = (ObjectName) args[0]; |
|
1156
bbc2d15aaf7a
5072476: RFE: support cascaded (federated) MBean Servers
dfuchs
parents:
1004
diff
changeset
|
215 |
final EventClient evtClient = getEventClient(); |
1004 | 216 |
|
1156
bbc2d15aaf7a
5072476: RFE: support cascaded (federated) MBean Servers
dfuchs
parents:
1004
diff
changeset
|
217 |
// Fails if evtClient is null AND the MBean we try to listen to is |
1004 | 218 |
// in a subnamespace. We fail here because we know this will not |
219 |
// work. |
|
220 |
// |
|
221 |
// Note that if the wrapped MBeanServerConnection points to a an |
|
222 |
// earlier agent (JDK 1.6 or earlier), then the EventClient will |
|
223 |
// be null (we can't use the event service with earlier JDKs). |
|
224 |
// |
|
1156
bbc2d15aaf7a
5072476: RFE: support cascaded (federated) MBean Servers
dfuchs
parents:
1004
diff
changeset
|
225 |
// In principle a null evtClient indicates that the remote VM is of |
1004 | 226 |
// an earlier version, in which case it shouldn't contain any namespace. |
227 |
// |
|
1156
bbc2d15aaf7a
5072476: RFE: support cascaded (federated) MBean Servers
dfuchs
parents:
1004
diff
changeset
|
228 |
// So having a null evtClient AND an MBean contained in a namespace is |
1004 | 229 |
// clearly an error case. |
230 |
// |
|
1156
bbc2d15aaf7a
5072476: RFE: support cascaded (federated) MBean Servers
dfuchs
parents:
1004
diff
changeset
|
231 |
if (evtClient == null) { |
1004 | 232 |
final String domain = mbean.getDomain(); |
1156
bbc2d15aaf7a
5072476: RFE: support cascaded (federated) MBean Servers
dfuchs
parents:
1004
diff
changeset
|
233 |
final int index = domain.indexOf(JMXNamespaces.NAMESPACE_SEPARATOR); |
1004 | 234 |
if (index > -1 && index < |
235 |
(domain.length()-NAMESPACE_SEPARATOR_LENGTH)) { |
|
236 |
throw new UnsupportedOperationException(method.getName()+ |
|
237 |
" on namespace "+domain.substring(0,index+ |
|
238 |
NAMESPACE_SEPARATOR_LENGTH)); |
|
239 |
} |
|
240 |
} |
|
241 |
||
242 |
if (methodName.equals("addNotificationListener")) { |
|
243 |
/* The various throws of IllegalArgumentException here |
|
244 |
should not happen, since we know what the methods in |
|
245 |
NotificationBroadcaster and NotificationEmitter |
|
246 |
are. */ |
|
247 |
if (nargs != 4) { |
|
248 |
final String msg = |
|
249 |
"Bad arg count to addNotificationListener: " + nargs; |
|
250 |
throw new IllegalArgumentException(msg); |
|
251 |
} |
|
252 |
/* Other inconsistencies will produce ClassCastException |
|
253 |
below. */ |
|
254 |
||
255 |
final NotificationListener listener = (NotificationListener) args[1]; |
|
256 |
final NotificationFilter filter = (NotificationFilter) args[2]; |
|
257 |
final Object handback = args[3]; |
|
258 |
||
1156
bbc2d15aaf7a
5072476: RFE: support cascaded (federated) MBean Servers
dfuchs
parents:
1004
diff
changeset
|
259 |
if (evtClient != null) { |
1004 | 260 |
// general case |
1156
bbc2d15aaf7a
5072476: RFE: support cascaded (federated) MBean Servers
dfuchs
parents:
1004
diff
changeset
|
261 |
evtClient.addNotificationListener(mbean,listener,filter,handback); |
1004 | 262 |
} else { |
263 |
// deprecated case. Only works for mbean in local namespace. |
|
264 |
connection.addNotificationListener(mbean,listener,filter, |
|
265 |
handback); |
|
266 |
} |
|
267 |
return null; |
|
268 |
||
269 |
} else if (methodName.equals("removeNotificationListener")) { |
|
270 |
||
271 |
/* NullPointerException if method with no args, but that |
|
272 |
shouldn't happen because removeNL does have args. */ |
|
273 |
NotificationListener listener = (NotificationListener) args[1]; |
|
274 |
||
275 |
switch (nargs) { |
|
276 |
case 2: |
|
1156
bbc2d15aaf7a
5072476: RFE: support cascaded (federated) MBean Servers
dfuchs
parents:
1004
diff
changeset
|
277 |
if (evtClient != null) { |
1004 | 278 |
// general case |
1156
bbc2d15aaf7a
5072476: RFE: support cascaded (federated) MBean Servers
dfuchs
parents:
1004
diff
changeset
|
279 |
evtClient.removeNotificationListener(mbean,listener); |
1004 | 280 |
} else { |
281 |
// deprecated case. Only works for mbean in local namespace. |
|
282 |
connection.removeNotificationListener(mbean, listener); |
|
283 |
} |
|
284 |
return null; |
|
285 |
||
286 |
case 4: |
|
287 |
NotificationFilter filter = (NotificationFilter) args[2]; |
|
288 |
Object handback = args[3]; |
|
1156
bbc2d15aaf7a
5072476: RFE: support cascaded (federated) MBean Servers
dfuchs
parents:
1004
diff
changeset
|
289 |
if (evtClient != null) { |
bbc2d15aaf7a
5072476: RFE: support cascaded (federated) MBean Servers
dfuchs
parents:
1004
diff
changeset
|
290 |
evtClient.removeNotificationListener(mbean, |
1004 | 291 |
listener, |
292 |
filter, |
|
293 |
handback); |
|
294 |
} else { |
|
295 |
connection.removeNotificationListener(mbean, |
|
296 |
listener, |
|
297 |
filter, |
|
298 |
handback); |
|
299 |
} |
|
300 |
return null; |
|
301 |
||
302 |
default: |
|
303 |
final String msg = |
|
304 |
"Bad arg count to removeNotificationListener: " + nargs; |
|
305 |
throw new IllegalArgumentException(msg); |
|
306 |
} |
|
307 |
||
308 |
} else { |
|
309 |
throw new IllegalArgumentException("Bad method name: " + |
|
310 |
methodName); |
|
311 |
} |
|
312 |
} |
|
313 |
||
314 |
private boolean shouldDoLocally(Object proxy, Method method) { |
|
315 |
final String methodName = method.getName(); |
|
316 |
if ((methodName.equals("hashCode") || methodName.equals("toString")) |
|
317 |
&& method.getParameterTypes().length == 0 |
|
318 |
&& isLocal(proxy, method)) |
|
319 |
return true; |
|
320 |
if (methodName.equals("equals") |
|
321 |
&& Arrays.equals(method.getParameterTypes(), |
|
322 |
new Class[] {Object.class}) |
|
323 |
&& isLocal(proxy, method)) |
|
324 |
return true; |
|
325 |
return false; |
|
326 |
} |
|
327 |
||
328 |
private Object doLocally(Object proxy, Method method, Object[] args) { |
|
329 |
final String methodName = method.getName(); |
|
330 |
||
331 |
if (methodName.equals("equals")) { |
|
332 |
||
333 |
if (this == args[0]) { |
|
334 |
return true; |
|
335 |
} |
|
336 |
||
337 |
if (!(args[0] instanceof Proxy)) { |
|
338 |
return false; |
|
339 |
} |
|
340 |
||
341 |
final InvocationHandler ihandler = |
|
342 |
Proxy.getInvocationHandler(args[0]); |
|
343 |
||
344 |
if (ihandler == null || |
|
345 |
!(ihandler instanceof EventClientConnection)) { |
|
346 |
return false; |
|
347 |
} |
|
348 |
||
349 |
final EventClientConnection handler = |
|
350 |
(EventClientConnection)ihandler; |
|
351 |
||
352 |
return connection.equals(handler.connection) && |
|
353 |
proxy.getClass().equals(args[0].getClass()); |
|
354 |
} else if (methodName.equals("hashCode")) { |
|
355 |
return connection.hashCode(); |
|
356 |
} |
|
357 |
||
358 |
throw new RuntimeException("Unexpected method name: " + methodName); |
|
359 |
} |
|
360 |
||
361 |
private static boolean isLocal(Object proxy, Method method) { |
|
362 |
final Class<?>[] interfaces = proxy.getClass().getInterfaces(); |
|
363 |
if(interfaces == null) { |
|
364 |
return true; |
|
365 |
} |
|
366 |
||
367 |
final String methodName = method.getName(); |
|
368 |
final Class<?>[] params = method.getParameterTypes(); |
|
369 |
for (Class<?> intf : interfaces) { |
|
370 |
try { |
|
371 |
intf.getMethod(methodName, params); |
|
372 |
return false; // found method in one of our interfaces |
|
373 |
} catch (NoSuchMethodException nsme) { |
|
374 |
// OK. |
|
375 |
} |
|
376 |
} |
|
377 |
||
378 |
return true; // did not find in any interface |
|
379 |
} |
|
380 |
||
381 |
/** |
|
382 |
* Return the EventClient used by this object. Can be null if the |
|
383 |
* remote VM is of an earlier JDK version which doesn't have the |
|
384 |
* event service.<br> |
|
385 |
* This method will invoke the event client factory the first time |
|
386 |
* it is called. |
|
387 |
**/ |
|
388 |
public final EventClient getEventClient() { |
|
389 |
if (initialized) return client; |
|
390 |
try { |
|
391 |
if (!lock.tryLock(TRYLOCK_TIMEOUT,TimeUnit.SECONDS)) |
|
392 |
throw new IllegalStateException("can't acquire lock"); |
|
393 |
try { |
|
394 |
client = eventClientFactory.call(); |
|
395 |
initialized = true; |
|
396 |
} finally { |
|
397 |
lock.unlock(); |
|
398 |
} |
|
399 |
} catch (RuntimeException x) { |
|
400 |
throw x; |
|
401 |
} catch (Exception x) { |
|
402 |
throw new IllegalStateException("Can't create EventClient: "+x,x); |
|
403 |
} |
|
404 |
return client; |
|
405 |
} |
|
406 |
||
407 |
/** |
|
408 |
* Returns an event client for the wrapped {@code MBeanServerConnection}. |
|
409 |
* This is the method invoked by the default event client factory. |
|
410 |
* @param connection the wrapped {@code MBeanServerConnection}. |
|
411 |
**/ |
|
412 |
protected EventClient createEventClient(MBeanServerConnection connection) |
|
413 |
throws Exception { |
|
414 |
final ObjectName name = |
|
415 |
EventClientDelegate.OBJECT_NAME; |
|
416 |
if (connection.isRegistered(name)) { |
|
417 |
return new EventClient(connection); |
|
418 |
} |
|
419 |
return null; |
|
420 |
} |
|
421 |
||
422 |
/** |
|
423 |
* Creates a new {@link MBeanServerConnection} that goes through an |
|
424 |
* {@link EventClient} to receive/subscribe to notifications. |
|
425 |
* @param connection the underlying {@link MBeanServerConnection}. |
|
426 |
* The given <code>connection</code> shouldn't be already |
|
427 |
* using an {@code EventClient}. |
|
428 |
* @param eventClientFactory a factory object that will be invoked |
|
429 |
* to create an {@link EventClient} when needed. |
|
430 |
* The {@code EventClient} is created lazily, when it is needed |
|
431 |
* for the first time. If null, a default factory will be used |
|
432 |
* (see {@link #createEventClient}). |
|
433 |
* @return the |
|
434 |
**/ |
|
435 |
public static MBeanServerConnection getEventConnectionFor( |
|
436 |
MBeanServerConnection connection, |
|
437 |
Callable<EventClient> eventClientFactory) { |
|
438 |
// if c already uses an EventClient no need to create a new one. |
|
439 |
// |
|
440 |
if (connection instanceof EventClientFactory |
|
441 |
&& eventClientFactory != null) |
|
442 |
throw new IllegalArgumentException("connection already uses EventClient"); |
|
443 |
||
444 |
if (connection instanceof EventClientFactory) |
|
445 |
return connection; |
|
446 |
||
447 |
// create a new proxy using an event client. |
|
448 |
// |
|
449 |
if (LOG.isLoggable(Level.FINE)) |
|
450 |
LOG.fine("Creating EventClient for: "+connection); |
|
451 |
return newProxyInstance(connection, |
|
452 |
MBeanServerConnection.class, |
|
453 |
eventClientFactory); |
|
454 |
} |
|
455 |
||
456 |
private Object invokeEventClientSubscriberMethod(Object proxy, |
|
457 |
Method method, Object[] args) throws Throwable { |
|
458 |
return call(this,method,args); |
|
459 |
} |
|
460 |
||
461 |
// Maximum lock timeout in seconds. Obviously arbitrary. |
|
462 |
// |
|
463 |
private final static short TRYLOCK_TIMEOUT = 3; |
|
464 |
||
465 |
private final MBeanServerConnection connection; |
|
466 |
private final Callable<EventClient> eventClientFactory; |
|
467 |
private final Lock lock; |
|
468 |
private volatile EventClient client = null; |
|
469 |
private volatile boolean initialized = false; |
|
470 |
||
471 |
} |