|
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.defaults.JmxProperties; |
|
29 import com.sun.jmx.mbeanserver.Util; |
|
30 import com.sun.jmx.namespace.JMXNamespaceUtils; |
|
31 import com.sun.jmx.namespace.NamespaceInterceptor.DynamicProbe; |
|
32 import com.sun.jmx.remote.util.EnvHelp; |
|
33 |
|
34 import java.io.IOException; |
|
35 import java.security.AccessControlException; |
|
36 import java.util.HashMap; |
|
37 import java.util.Map; |
|
38 import java.util.logging.Level; |
|
39 import java.util.logging.Logger; |
|
40 |
|
41 import javax.management.AttributeChangeNotification; |
|
42 |
|
43 import javax.management.InstanceNotFoundException; |
|
44 import javax.management.ListenerNotFoundException; |
|
45 import javax.management.MBeanNotificationInfo; |
|
46 import javax.management.MBeanPermission; |
|
47 import javax.management.MBeanServerConnection; |
|
48 import javax.management.MalformedObjectNameException; |
|
49 import javax.management.Notification; |
|
50 import javax.management.NotificationBroadcasterSupport; |
|
51 import javax.management.NotificationEmitter; |
|
52 import javax.management.NotificationFilter; |
|
53 import javax.management.NotificationListener; |
|
54 import javax.management.ObjectName; |
|
55 import javax.management.event.EventClient; |
|
56 import javax.management.remote.JMXConnectionNotification; |
|
57 import javax.management.remote.JMXConnector; |
|
58 import javax.management.remote.JMXConnectorFactory; |
|
59 import javax.management.remote.JMXServiceURL; |
|
60 |
|
61 /** |
|
62 * A {@link JMXNamespace} that will connect to a remote MBeanServer |
|
63 * by creating a {@link javax.management.remote.JMXConnector} from a |
|
64 * {@link javax.management.remote.JMXServiceURL}. |
|
65 * <p> |
|
66 * You can call {@link #connect() connect()} and {@link #close close()} |
|
67 * several times. This MBean will emit an {@link AttributeChangeNotification} |
|
68 * when the value of its {@link #isConnected Connected} attribute changes. |
|
69 * </p> |
|
70 * <p> |
|
71 * The JMX Remote Namespace MBean is not connected until {@link |
|
72 * #connect() connect()} is explicitly called. The usual sequence of code to |
|
73 * create a JMX Remote Namespace is thus: |
|
74 * </p> |
|
75 * <pre> |
|
76 * final String namespace = "mynamespace"; |
|
77 * final ObjectName name = {@link JMXNamespaces#getNamespaceObjectName |
|
78 * JMXNamespaces.getNamespaceObjectName(namespace)}; |
|
79 * final JMXServiceURL remoteServerURL = .... ; |
|
80 * final Map<String,Object> optionsMap = .... ; |
|
81 * final MBeanServer masterMBeanServer = .... ; |
|
82 * final JMXRemoteNamespace namespaceMBean = {@link #newJMXRemoteNamespace |
|
83 * JMXRemoteNamespace.newJMXRemoteNamespace(remoteServerURL, optionsMap)}; |
|
84 * masterMBeanServer.registerMBean(namespaceMBean, name); |
|
85 * namespaceMBean.connect(); |
|
86 * // or: masterMBeanServer.invoke(name, {@link #connect() "connect"}, null, null); |
|
87 * </pre> |
|
88 * <p> |
|
89 * The JMX Remote Namespace MBean will register for {@linkplain |
|
90 * JMXConnectionNotification JMX Connection Notifications} with its underlying |
|
91 * {@link JMXConnector}. When a JMX Connection Notification indicates that |
|
92 * the underlying connection has failed, the JMX Remote Namespace MBean |
|
93 * closes its underlying connector and switches its {@link #isConnected |
|
94 * Connected} attribute to false, emitting an {@link |
|
95 * AttributeChangeNotification}. |
|
96 * </p> |
|
97 * <p> |
|
98 * At this point, a managing application (or an administrator connected |
|
99 * through a management console) can attempt to reconnect the |
|
100 * JMX Remote Namespace MBean by calling its {@link #connect() connect()} method |
|
101 * again. |
|
102 * </p> |
|
103 * <p>Note that when the connection with the remote namespace fails, or when |
|
104 * {@link #close} is called, then any notification subscription to |
|
105 * MBeans registered in that namespace will be lost - unless a custom |
|
106 * {@linkplain javax.management.event event service} supporting connection-less |
|
107 * mode was used. |
|
108 * </p> |
|
109 * @since 1.7 |
|
110 */ |
|
111 public class JMXRemoteNamespace |
|
112 extends JMXNamespace |
|
113 implements JMXRemoteNamespaceMBean, NotificationEmitter { |
|
114 |
|
115 /** |
|
116 * A logger for this class. |
|
117 */ |
|
118 private static final Logger LOG = JmxProperties.NAMESPACE_LOGGER; |
|
119 |
|
120 private static final Logger PROBE_LOG = Logger.getLogger( |
|
121 JmxProperties.NAMESPACE_LOGGER_NAME+".probe"); |
|
122 |
|
123 |
|
124 // This connection listener is used to listen for connection events from |
|
125 // the underlying JMXConnector. It is used in particular to maintain the |
|
126 // "connected" state in this MBean. |
|
127 // |
|
128 private static class ConnectionListener implements NotificationListener { |
|
129 private final JMXRemoteNamespace handler; |
|
130 private ConnectionListener(JMXRemoteNamespace handler) { |
|
131 this.handler = handler; |
|
132 } |
|
133 public void handleNotification(Notification notification, |
|
134 Object handback) { |
|
135 if (!(notification instanceof JMXConnectionNotification)) |
|
136 return; |
|
137 final JMXConnectionNotification cn = |
|
138 (JMXConnectionNotification)notification; |
|
139 handler.checkState(this,cn,(JMXConnector)handback); |
|
140 } |
|
141 } |
|
142 |
|
143 // When the JMXRemoteNamespace is originally created, it is not connected, |
|
144 // which means that the source MBeanServer should be one that throws |
|
145 // exceptions for most methods. When it is subsequently connected, |
|
146 // the methods should be forwarded to the MBeanServerConnection. |
|
147 // We handle this using MBeanServerConnectionWrapper. The |
|
148 // MBeanServerConnection that is supplied to the constructor of |
|
149 // MBeanServerConnectionWrapper is ignored (and in fact it is null) |
|
150 // because the one that is actually used is the one supplied by the |
|
151 // override of getMBeanServerConnection(). |
|
152 private static class JMXRemoteNamespaceDelegate |
|
153 extends MBeanServerConnectionWrapper |
|
154 implements DynamicProbe { |
|
155 private volatile JMXRemoteNamespace parent=null; |
|
156 |
|
157 JMXRemoteNamespaceDelegate() { |
|
158 super(null,null); |
|
159 } |
|
160 @Override |
|
161 public MBeanServerConnection getMBeanServerConnection() { |
|
162 return parent.getMBeanServerConnection(); |
|
163 } |
|
164 @Override |
|
165 public ClassLoader getDefaultClassLoader() { |
|
166 return parent.getDefaultClassLoader(); |
|
167 } |
|
168 |
|
169 // Because this class is instantiated in the super() call from the |
|
170 // constructor of JMXRemoteNamespace, it cannot be an inner class. |
|
171 // This method achieves the effect that an inner class would have |
|
172 // had, of giving the class a reference to the outer "this". |
|
173 synchronized void initParentOnce(JMXRemoteNamespace parent) { |
|
174 if (this.parent != null) |
|
175 throw new UnsupportedOperationException("parent already set"); |
|
176 this.parent=parent; |
|
177 |
|
178 } |
|
179 |
|
180 public boolean isProbeRequested() { |
|
181 return this.parent.isProbeRequested(); |
|
182 } |
|
183 } |
|
184 |
|
185 private static final MBeanNotificationInfo connectNotification = |
|
186 new MBeanNotificationInfo(new String[] { |
|
187 AttributeChangeNotification.ATTRIBUTE_CHANGE}, |
|
188 "Connected", |
|
189 "Emitted when the Connected state of this object changes"); |
|
190 |
|
191 private static long seqNumber=0; |
|
192 |
|
193 private final NotificationBroadcasterSupport broadcaster; |
|
194 private final ConnectionListener listener; |
|
195 private final JMXServiceURL jmxURL; |
|
196 private final Map<String,?> optionsMap; |
|
197 |
|
198 private volatile MBeanServerConnection server = null; |
|
199 private volatile JMXConnector conn = null; |
|
200 private volatile ClassLoader defaultClassLoader = null; |
|
201 private volatile boolean probed; |
|
202 |
|
203 /** |
|
204 * Creates a new instance of {@code JMXRemoteNamespace}. |
|
205 * <p> |
|
206 * This constructor is provided for subclasses. |
|
207 * To create a new instance of {@code JMXRemoteNamespace} call |
|
208 * {@link #newJMXRemoteNamespace |
|
209 * JMXRemoteNamespace.newJMXRemoteNamespace(sourceURL, optionsMap)}. |
|
210 * </p> |
|
211 * @param sourceURL a JMX service URL that can be used to {@linkplain |
|
212 * #connect() connect} to the |
|
213 * source MBean Server. The source MBean Server is the remote |
|
214 * MBean Server which contains the MBeans that will be mirrored |
|
215 * in this namespace. |
|
216 * @param optionsMap the options map that will be passed to the |
|
217 * {@link JMXConnectorFactory} when {@linkplain |
|
218 * JMXConnectorFactory#newJMXConnector creating} the |
|
219 * {@link JMXConnector} used to {@linkplain #connect() connect} |
|
220 * to the remote source MBean Server. Can be null, which is |
|
221 * equivalent to an empty map. |
|
222 * @see #newJMXRemoteNamespace JMXRemoteNamespace.newJMXRemoteNamespace |
|
223 * @see #connect |
|
224 */ |
|
225 protected JMXRemoteNamespace(JMXServiceURL sourceURL, |
|
226 Map<String,?> optionsMap) { |
|
227 super(new JMXRemoteNamespaceDelegate()); |
|
228 ((JMXRemoteNamespaceDelegate)super.getSourceServer()). |
|
229 initParentOnce(this); |
|
230 |
|
231 // URL must not be null. |
|
232 this.jmxURL = JMXNamespaceUtils.checkNonNull(sourceURL,"url"); |
|
233 this.broadcaster = |
|
234 new NotificationBroadcasterSupport(connectNotification); |
|
235 |
|
236 // handles options |
|
237 this.optionsMap = JMXNamespaceUtils.unmodifiableMap(optionsMap); |
|
238 |
|
239 // handles (dis)connection events |
|
240 this.listener = new ConnectionListener(this); |
|
241 |
|
242 // XXX TODO: remove the probe, or simplify it. |
|
243 this.probed = false; |
|
244 } |
|
245 |
|
246 /** |
|
247 * Returns the {@code JMXServiceURL} that is (or will be) used to |
|
248 * connect to the remote name space. <p> |
|
249 * @see #connect |
|
250 * @return The {@code JMXServiceURL} used to connect to the remote |
|
251 * name space. |
|
252 */ |
|
253 public JMXServiceURL getJMXServiceURL() { |
|
254 return jmxURL; |
|
255 } |
|
256 |
|
257 /** |
|
258 * In this class, this method never returns {@code null}, and the |
|
259 * address returned is the {@link #getJMXServiceURL JMXServiceURL} |
|
260 * that is used by this object to {@linkplain #connect} to the remote |
|
261 * name space. <p> |
|
262 * This behaviour might be overriden by subclasses, if needed. |
|
263 * For instance, a subclass might want to return {@code null} if it |
|
264 * doesn't want to expose that JMXServiceURL. |
|
265 */ |
|
266 public JMXServiceURL getAddress() { |
|
267 return getJMXServiceURL(); |
|
268 } |
|
269 |
|
270 private Map<String,?> getEnvMap() { |
|
271 return optionsMap; |
|
272 } |
|
273 |
|
274 boolean isProbeRequested() { |
|
275 return probed==false; |
|
276 } |
|
277 |
|
278 public void addNotificationListener(NotificationListener listener, |
|
279 NotificationFilter filter, Object handback) { |
|
280 broadcaster.addNotificationListener(listener, filter, handback); |
|
281 } |
|
282 |
|
283 /** |
|
284 * A subclass that needs to send its own notifications must override |
|
285 * this method in order to return an {@link MBeanNotificationInfo |
|
286 * MBeanNotificationInfo[]} array containing both its own notification |
|
287 * infos and the notification infos of its super class. <p> |
|
288 * The implementation should probably look like: |
|
289 * <pre> |
|
290 * final MBeanNotificationInfo[] myOwnNotifs = { .... }; |
|
291 * final MBeanNotificationInfo[] parentNotifs = |
|
292 * super.getNotificationInfo(); |
|
293 * final Set<MBeanNotificationInfo> mergedResult = |
|
294 * new HashSet<MBeanNotificationInfo>(); |
|
295 * mergedResult.addAll(Arrays.asList(myOwnNotifs)); |
|
296 * mergedResult.addAll(Arrays.asList(parentNotifs)); |
|
297 * return mergeResult.toArray( |
|
298 * new MBeanNotificationInfo[mergedResult.size()]); |
|
299 * </pre> |
|
300 */ |
|
301 public MBeanNotificationInfo[] getNotificationInfo() { |
|
302 return broadcaster.getNotificationInfo(); |
|
303 } |
|
304 |
|
305 public void removeNotificationListener(NotificationListener listener) |
|
306 throws ListenerNotFoundException { |
|
307 broadcaster.removeNotificationListener(listener); |
|
308 } |
|
309 |
|
310 public void removeNotificationListener(NotificationListener listener, |
|
311 NotificationFilter filter, Object handback) |
|
312 throws ListenerNotFoundException { |
|
313 broadcaster.removeNotificationListener(listener, filter, handback); |
|
314 } |
|
315 |
|
316 private static synchronized long getNextSeqNumber() { |
|
317 return seqNumber++; |
|
318 } |
|
319 |
|
320 |
|
321 /** |
|
322 * Sends a notification to registered listeners. Before the notification |
|
323 * is sent, the following steps are performed: |
|
324 * <ul><li> |
|
325 * If {@code n.getSequenceNumber() <= 0} set it to the next available |
|
326 * sequence number.</li> |
|
327 * <li>If {@code n.getSource() == null}, set it to the value returned by {@link |
|
328 * #getObjectName getObjectName()}. |
|
329 * </li></ul> |
|
330 * <p>This method can be called by subclasses in order to send their own |
|
331 * notifications. |
|
332 * In that case, these subclasses might also need to override |
|
333 * {@link #getNotificationInfo} in order to declare their own |
|
334 * {@linkplain MBeanNotificationInfo notification types}. |
|
335 * </p> |
|
336 * @param n The notification to send to registered listeners. |
|
337 * @see javax.management.NotificationBroadcasterSupport |
|
338 * @see #getNotificationInfo |
|
339 **/ |
|
340 protected void sendNotification(Notification n) { |
|
341 if (n.getSequenceNumber()<=0) |
|
342 n.setSequenceNumber(getNextSeqNumber()); |
|
343 if (n.getSource()==null) |
|
344 n.setSource(getObjectName()); |
|
345 broadcaster.sendNotification(n); |
|
346 } |
|
347 |
|
348 private void checkState(ConnectionListener listener, |
|
349 JMXConnectionNotification cn, |
|
350 JMXConnector emittingConnector) { |
|
351 |
|
352 // Due to the asynchronous handling of notifications, it is |
|
353 // possible that this method is called for a JMXConnector |
|
354 // (or connection) which is already closed and replaced by a newer |
|
355 // one. |
|
356 // |
|
357 // This method attempts to determine the real state of the |
|
358 // connection - which might be different from what the notification |
|
359 // says. |
|
360 // |
|
361 // This is quite complex logic - because we try not to hold any |
|
362 // lock while evaluating the true value of the connected state, |
|
363 // while anyone might also call close() or connect() from a |
|
364 // different thread. |
|
365 // |
|
366 // The method switchConnection() (called from here too) also has the |
|
367 // same kind of complex logic. |
|
368 // |
|
369 // We use the JMXConnector has a handback to the notification listener |
|
370 // (emittingConnector) in order to be able to determine whether the |
|
371 // notification concerns the current connector in use, or an older |
|
372 // one. |
|
373 // |
|
374 boolean remove = false; |
|
375 |
|
376 // whether the emittingConnector is already 'removed' |
|
377 synchronized (this) { |
|
378 if (this.conn != emittingConnector || |
|
379 JMXConnectionNotification.FAILED.equals(cn.getType())) |
|
380 remove = true; |
|
381 } |
|
382 |
|
383 // We need to unregister our listener from this 'removed' connector. |
|
384 // This is the only place where we remove the listener. |
|
385 // |
|
386 if (remove) { |
|
387 try { |
|
388 // This may fail if the connector is already closed. |
|
389 // But better unregister anyway... |
|
390 // |
|
391 emittingConnector.removeConnectionNotificationListener( |
|
392 listener,null, |
|
393 emittingConnector); |
|
394 } catch (Exception x) { |
|
395 LOG.log(Level.FINE, |
|
396 "Failed to unregister connection listener"+x); |
|
397 LOG.log(Level.FINEST, |
|
398 "Failed to unregister connection listener",x); |
|
399 } |
|
400 try { |
|
401 // This may fail if the connector is already closed. |
|
402 // But better call close twice and get an exception than |
|
403 // leaking... |
|
404 // |
|
405 emittingConnector.close(); |
|
406 } catch (Exception x) { |
|
407 LOG.log(Level.FINEST, |
|
408 "Failed to close old connector " + |
|
409 "(failure was expected): "+x); |
|
410 } |
|
411 } |
|
412 |
|
413 // Now we checked whether our current connector is still alive. |
|
414 // |
|
415 boolean closed = false; |
|
416 final JMXConnector thisconn = this.conn; |
|
417 try { |
|
418 if (thisconn != null) |
|
419 thisconn.getConnectionId(); |
|
420 } catch (IOException x) { |
|
421 LOG.finest("Connector already closed: "+x); |
|
422 closed = true; |
|
423 } |
|
424 |
|
425 // We got an IOException - the connector is not connected. |
|
426 // Need to forget it and switch our state to closed. |
|
427 // |
|
428 if (closed) { |
|
429 switchConnection(thisconn,null,null); |
|
430 try { |
|
431 // Usually this will fail... Better call close twice |
|
432 // and get an exception than leaking... |
|
433 // |
|
434 if (thisconn != emittingConnector || !remove) |
|
435 thisconn.close(); |
|
436 } catch (IOException x) { |
|
437 LOG.log(Level.FINEST, |
|
438 "Failed to close connector (failure was expected): " |
|
439 +x); |
|
440 } |
|
441 } |
|
442 } |
|
443 |
|
444 private final void switchConnection(JMXConnector oldc, |
|
445 JMXConnector newc, |
|
446 MBeanServerConnection mbs) { |
|
447 boolean connect = false; |
|
448 boolean close = false; |
|
449 synchronized (this) { |
|
450 if (oldc != conn) { |
|
451 if (newc != null) { |
|
452 try { |
|
453 newc.close(); |
|
454 } catch (IOException x) { |
|
455 LOG.log(Level.FINEST, |
|
456 "Failed to close connector",x); |
|
457 } |
|
458 } |
|
459 return; |
|
460 } |
|
461 if (conn == null && newc != null) connect=true; |
|
462 if (newc == null && conn != null) close = true; |
|
463 conn = newc; |
|
464 server = mbs; |
|
465 } |
|
466 if (connect || close) { |
|
467 boolean oldstate = close; |
|
468 boolean newstate = connect; |
|
469 final ObjectName myName = getObjectName(); |
|
470 |
|
471 // In the uncommon case where the MBean is connected before |
|
472 // being registered, myName can be null... |
|
473 // If myName is null - we use 'this' as the source instead... |
|
474 // |
|
475 final Object source = (myName==null)?this:myName; |
|
476 final AttributeChangeNotification acn = |
|
477 new AttributeChangeNotification(source, |
|
478 getNextSeqNumber(),System.currentTimeMillis(), |
|
479 String.valueOf(source)+ |
|
480 (newstate?" connected":" closed"), |
|
481 "Connected", |
|
482 "boolean", |
|
483 Boolean.valueOf(oldstate), |
|
484 Boolean.valueOf(newstate)); |
|
485 sendNotification(acn); |
|
486 } |
|
487 } |
|
488 |
|
489 private void closeall(JMXConnector... a) { |
|
490 for (JMXConnector c : a) { |
|
491 try { |
|
492 if (c != null) c.close(); |
|
493 } catch (Exception x) { |
|
494 // OK: we're gonna throw the original exception later. |
|
495 LOG.finest("Ignoring exception when closing connector: "+x); |
|
496 } |
|
497 } |
|
498 } |
|
499 |
|
500 JMXConnector connect(JMXServiceURL url, Map<String,?> env) |
|
501 throws IOException { |
|
502 final JMXConnector c = newJMXConnector(jmxURL, env); |
|
503 c.connect(env); |
|
504 return c; |
|
505 } |
|
506 |
|
507 /** |
|
508 * Creates a new JMXConnector with the specified {@code url} and |
|
509 * {@code env} options map. |
|
510 * <p> |
|
511 * This method first calls {@link JMXConnectorFactory#newJMXConnector |
|
512 * JMXConnectorFactory.newJMXConnector(jmxURL, env)} to obtain a new |
|
513 * JMX connector, and returns that. |
|
514 * </p> |
|
515 * <p> |
|
516 * A subclass of {@link JMXRemoteNamespace} can provide an implementation |
|
517 * that connects to a sub namespace of the remote server by subclassing |
|
518 * this class in the following way: |
|
519 * <pre> |
|
520 * class JMXRemoteSubNamespace extends JMXRemoteNamespace { |
|
521 * private final String subnamespace; |
|
522 * JMXRemoteSubNamespace(JMXServiceURL url, |
|
523 * Map{@code <String,?>} env, String subnamespace) { |
|
524 * super(url,options); |
|
525 * this.subnamespace = subnamespace; |
|
526 * } |
|
527 * protected JMXConnector newJMXConnector(JMXServiceURL url, |
|
528 * Map<String,?> env) throws IOException { |
|
529 * final JMXConnector inner = super.newJMXConnector(url,env); |
|
530 * return {@link JMXNamespaces#narrowToNamespace(JMXConnector,String) |
|
531 * JMXNamespaces.narrowToNamespace(inner,subnamespace)}; |
|
532 * } |
|
533 * } |
|
534 * </pre> |
|
535 * </p> |
|
536 * <p> |
|
537 * Some connectors, like the JMXMP connector server defined by the |
|
538 * version 1.2 of the JMX API may not have been upgraded to use the |
|
539 * new {@linkplain javax.management.event Event Service} defined in this |
|
540 * version of the JMX API. |
|
541 * <p> |
|
542 * In that case, and if the remote server to which this JMXRemoteNamespace |
|
543 * connects also contains namespaces, it may be necessary to configure |
|
544 * explicitly an {@linkplain |
|
545 * javax.management.event.EventClientDelegate#newForwarder() |
|
546 * Event Client Forwarder} on the remote server side, and to force the use |
|
547 * of an {@link EventClient} on this client side. |
|
548 * <br> |
|
549 * A subclass of {@link JMXRemoteNamespace} can provide an implementation |
|
550 * of {@code newJMXConnector} that will force notification subscriptions |
|
551 * to flow through an {@link EventClient} over a legacy protocol by |
|
552 * overriding this method in the following way: |
|
553 * </p> |
|
554 * <pre> |
|
555 * class JMXRemoteEventClientNamespace extends JMXRemoteNamespace { |
|
556 * JMXRemoteSubNamespaceConnector(JMXServiceURL url, |
|
557 * Map<String,?> env) { |
|
558 * super(url,options); |
|
559 * } |
|
560 * protected JMXConnector newJMXConnector(JMXServiceURL url, |
|
561 * Map<String,?> env) throws IOException { |
|
562 * final JMXConnector inner = super.newJMXConnector(url,env); |
|
563 * return {@link EventClient#withEventClient( |
|
564 * JMXConnector) EventClient.withEventClient(inner)}; |
|
565 * } |
|
566 * } |
|
567 * </pre> |
|
568 * <p> |
|
569 * Note that the remote server also needs to provide an {@link |
|
570 * javax.management.event.EventClientDelegateMBean}: only configuring |
|
571 * the client side (this object) is not enough.<br> |
|
572 * In summary, this technique should be used if the remote server |
|
573 * supports JMX namespaces, but uses a JMX Connector Server whose |
|
574 * implementation does not transparently use the new Event Service |
|
575 * (as would be the case with the JMXMPConnectorServer implementation |
|
576 * from the reference implementation of the JMX Remote API 1.0 |
|
577 * specification). |
|
578 * </p> |
|
579 * @param url The JMXServiceURL of the remote server. |
|
580 * @param optionsMap An unmodifiable options map that will be passed to the |
|
581 * {@link JMXConnectorFactory} when {@linkplain |
|
582 * JMXConnectorFactory#newJMXConnector creating} the |
|
583 * {@link JMXConnector} that can connect to the remote source |
|
584 * MBean Server. |
|
585 * @return An unconnected JMXConnector to use to connect to the remote |
|
586 * server |
|
587 * @throws java.io.IOException if the connector could not be created. |
|
588 * @see JMXConnectorFactory#newJMXConnector(javax.management.remote.JMXServiceURL, java.util.Map) |
|
589 * @see #JMXRemoteNamespace |
|
590 */ |
|
591 protected JMXConnector newJMXConnector(JMXServiceURL url, |
|
592 Map<String,?> optionsMap) throws IOException { |
|
593 final JMXConnector c = |
|
594 JMXConnectorFactory.newJMXConnector(jmxURL, optionsMap); |
|
595 // TODO: uncomment this when contexts are added |
|
596 // return ClientContext.withDynamicContext(c); |
|
597 return c; |
|
598 } |
|
599 |
|
600 public void connect() throws IOException { |
|
601 if (conn != null) { |
|
602 try { |
|
603 // This is much too fragile. It must go away! |
|
604 PROBE_LOG.finest("Probing again..."); |
|
605 triggerProbe(getMBeanServerConnection()); |
|
606 } catch(Exception x) { |
|
607 close(); |
|
608 Throwable cause = x; |
|
609 // if the cause is a security exception - rethrows it... |
|
610 while (cause != null) { |
|
611 if (cause instanceof SecurityException) |
|
612 throw (SecurityException) cause; |
|
613 cause = cause.getCause(); |
|
614 } |
|
615 throw new IOException("connection failed: cycle?",x); |
|
616 } |
|
617 } |
|
618 LOG.fine("connecting..."); |
|
619 // TODO remove these traces |
|
620 // System.err.println(getInitParameter()+" connecting"); |
|
621 final Map<String,Object> env = |
|
622 new HashMap<String,Object>(getEnvMap()); |
|
623 try { |
|
624 // XXX: We should probably document this... |
|
625 // This allows to specify a loader name - which will be |
|
626 // retrieved from the paret MBeanServer. |
|
627 defaultClassLoader = |
|
628 EnvHelp.resolveServerClassLoader(env,getMBeanServer()); |
|
629 } catch (InstanceNotFoundException x) { |
|
630 final IOException io = |
|
631 new IOException("ClassLoader not found"); |
|
632 io.initCause(x); |
|
633 throw io; |
|
634 } |
|
635 env.put(JMXConnectorFactory.DEFAULT_CLASS_LOADER,defaultClassLoader); |
|
636 final JMXServiceURL url = getJMXServiceURL(); |
|
637 final JMXConnector aconn = connect(url,env); |
|
638 final MBeanServerConnection msc; |
|
639 try { |
|
640 msc = aconn.getMBeanServerConnection(); |
|
641 aconn.addConnectionNotificationListener(listener,null,aconn); |
|
642 } catch (IOException io) { |
|
643 closeall(aconn); |
|
644 throw io; |
|
645 } catch (RuntimeException x) { |
|
646 closeall(aconn); |
|
647 throw x; |
|
648 } |
|
649 |
|
650 |
|
651 // XXX Revisit here |
|
652 // Note from the author: This business of switching connection is |
|
653 // incredibly complex. Isn't there any means to simplify it? |
|
654 // |
|
655 switchConnection(conn,aconn,msc); |
|
656 try { |
|
657 triggerProbe(msc); |
|
658 } catch(Exception x) { |
|
659 close(); |
|
660 Throwable cause = x; |
|
661 // if the cause is a security exception - rethrows it... |
|
662 while (cause != null) { |
|
663 if (cause instanceof SecurityException) |
|
664 throw (SecurityException) cause; |
|
665 cause = cause.getCause(); |
|
666 } |
|
667 throw new IOException("connection failed: cycle?",x); |
|
668 } |
|
669 LOG.fine("connected."); |
|
670 } |
|
671 |
|
672 // If this is a self-linking namespace, this method should trigger |
|
673 // the emission of a probe in the wrapping NamespaceInterceptor. |
|
674 // The first call to source() in the wrapping NamespaceInterceptor |
|
675 // causes the emission of the probe. |
|
676 // |
|
677 // Note: the MBeanServer returned by getSourceServer |
|
678 // (our private JMXRemoteNamespaceDelegate inner class) |
|
679 // implements a sun private interface (DynamicProbe) which is |
|
680 // used by the NamespaceInterceptor to determine whether it should |
|
681 // send a probe or not. |
|
682 // We needed this interface here because the NamespaceInterceptor |
|
683 // has otherwise no means to knows that this object has just |
|
684 // connected, and that a new probe should be sent. |
|
685 // |
|
686 // Probes work this way: the NamespaceInterceptor sets a flag and sends |
|
687 // a queryNames() request. If a queryNames() request comes in when the flag |
|
688 // is on, then it deduces that there is a self-linking loop - and instead |
|
689 // of calling queryNames() on the JMXNamespace (which would cause the |
|
690 // loop to go on) it breaks the recursion by returning the probe ObjectName. |
|
691 // If the NamespaceInterceptor receives the probe ObjectName as result of |
|
692 // its original queryNames() it knows that it has been looping back on |
|
693 // itslef and throws an Exception - which will be raised through this |
|
694 // method, thus preventing the connection to be established... |
|
695 // |
|
696 // More info in the com.sun.jmx.namespace.NamespaceInterceptor class |
|
697 // |
|
698 // XXX: TODO this probe thing is way too complex and fragile. |
|
699 // This *must* go away or be replaced by something simpler. |
|
700 // ideas are welcomed. |
|
701 // |
|
702 private void triggerProbe(final MBeanServerConnection msc) |
|
703 throws MalformedObjectNameException, IOException { |
|
704 // Query Pattern that we will send through the source server in order |
|
705 // to detect self-linking namespaces. |
|
706 // |
|
707 // |
|
708 final ObjectName pattern; |
|
709 pattern = ObjectName.getInstance("*" + |
|
710 JMXNamespaces.NAMESPACE_SEPARATOR + ":" + |
|
711 JMXNamespace.TYPE_ASSIGNMENT); |
|
712 probed = false; |
|
713 try { |
|
714 msc.queryNames(pattern, null); |
|
715 probed = true; |
|
716 } catch (AccessControlException x) { |
|
717 // if we have an MBeanPermission missing then do nothing... |
|
718 if (!(x.getPermission() instanceof MBeanPermission)) |
|
719 throw x; |
|
720 PROBE_LOG.finer("Can't check for cycles: " + x); |
|
721 probed = false; // no need to do it again... |
|
722 } |
|
723 } |
|
724 |
|
725 public void close() throws IOException { |
|
726 if (conn == null) return; |
|
727 LOG.fine("closing..."); |
|
728 // System.err.println(toString()+": closing..."); |
|
729 conn.close(); |
|
730 // System.err.println(toString()+": connector closed"); |
|
731 switchConnection(conn,null,null); |
|
732 LOG.fine("closed."); |
|
733 // System.err.println(toString()+": closed"); |
|
734 } |
|
735 |
|
736 MBeanServerConnection getMBeanServerConnection() { |
|
737 if (conn == null) |
|
738 throw newRuntimeIOException("getMBeanServerConnection: not connected"); |
|
739 return server; |
|
740 } |
|
741 |
|
742 // Better than throwing UndeclaredThrowableException ... |
|
743 private RuntimeException newRuntimeIOException(String msg) { |
|
744 final IllegalStateException illegal = new IllegalStateException(msg); |
|
745 return Util.newRuntimeIOException(new IOException(msg,illegal)); |
|
746 } |
|
747 |
|
748 /** |
|
749 * Returns the default class loader used by the underlying |
|
750 * {@link JMXConnector}. |
|
751 * @return the default class loader used when communicating with the |
|
752 * remote source MBean server. |
|
753 **/ |
|
754 ClassLoader getDefaultClassLoader() { |
|
755 if (conn == null) |
|
756 throw newRuntimeIOException("getMBeanServerConnection: not connected"); |
|
757 return defaultClassLoader; |
|
758 } |
|
759 |
|
760 public boolean isConnected() { |
|
761 // This is a pleonasm |
|
762 return (conn != null) && (server != null); |
|
763 } |
|
764 |
|
765 |
|
766 /** |
|
767 * This name space handler will automatically {@link #close} its |
|
768 * connection with the remote source in {@code preDeregister}. |
|
769 **/ |
|
770 @Override |
|
771 public void preDeregister() throws Exception { |
|
772 try { |
|
773 close(); |
|
774 } catch (IOException x) { |
|
775 LOG.fine("Failed to close properly - exception ignored: " + x); |
|
776 LOG.log(Level.FINEST, |
|
777 "Failed to close properly - exception ignored",x); |
|
778 } |
|
779 super.preDeregister(); |
|
780 } |
|
781 |
|
782 /** |
|
783 * This method calls {@link |
|
784 * javax.management.MBeanServerConnection#getMBeanCount |
|
785 * getMBeanCount()} on the remote namespace. |
|
786 * @throws java.io.IOException if an {@link IOException} is raised when |
|
787 * communicating with the remote source namespace. |
|
788 */ |
|
789 @Override |
|
790 public Integer getMBeanCount() throws IOException { |
|
791 return getMBeanServerConnection().getMBeanCount(); |
|
792 } |
|
793 |
|
794 /** |
|
795 * This method returns the result of calling {@link |
|
796 * javax.management.MBeanServerConnection#getDomains |
|
797 * getDomains()} on the remote namespace. |
|
798 * @throws java.io.IOException if an {@link IOException} is raised when |
|
799 * communicating with the remote source namespace. |
|
800 */ |
|
801 @Override |
|
802 public String[] getDomains() throws IOException { |
|
803 return getMBeanServerConnection().getDomains(); |
|
804 } |
|
805 |
|
806 /** |
|
807 * This method returns the result of calling {@link |
|
808 * javax.management.MBeanServerConnection#getDefaultDomain |
|
809 * getDefaultDomain()} on the remote namespace. |
|
810 * @throws java.io.IOException if an {@link IOException} is raised when |
|
811 * communicating with the remote source namespace. |
|
812 */ |
|
813 @Override |
|
814 public String getDefaultDomain() throws IOException { |
|
815 return getMBeanServerConnection().getDefaultDomain(); |
|
816 } |
|
817 |
|
818 /** |
|
819 * Creates a new instance of {@code JMXRemoteNamespace}. |
|
820 * @param sourceURL a JMX service URL that can be used to connect to the |
|
821 * source MBean Server. The source MBean Server is the remote |
|
822 * MBean Server which contains the MBeans that will be mirrored |
|
823 * in this namespace. |
|
824 * @param optionsMap An options map that will be passed to the |
|
825 * {@link JMXConnectorFactory} when {@linkplain |
|
826 * JMXConnectorFactory#newJMXConnector creating} the |
|
827 * {@link JMXConnector} used to connect to the remote source |
|
828 * MBean Server. Can be null, which is equivalent to an empty map. |
|
829 * @see #JMXRemoteNamespace JMXRemoteNamespace(sourceURL,optionsMap) |
|
830 * @see JMXConnectorFactory#newJMXConnector(javax.management.remote.JMXServiceURL, java.util.Map) |
|
831 */ |
|
832 public static JMXRemoteNamespace newJMXRemoteNamespace( |
|
833 JMXServiceURL sourceURL, |
|
834 Map<String,?> optionsMap) { |
|
835 return new JMXRemoteNamespace(sourceURL, optionsMap); |
|
836 } |
|
837 } |