/* * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package javax.management.namespace; import com.sun.jmx.defaults.JmxProperties; import com.sun.jmx.mbeanserver.Util; import com.sun.jmx.remote.util.EnvHelp; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import java.util.logging.Logger; import javax.management.AttributeChangeNotification; import javax.management.ClientContext; import javax.management.InstanceNotFoundException; import javax.management.ListenerNotFoundException; import javax.management.MBeanNotificationInfo; import javax.management.MBeanServerConnection; import javax.management.Notification; import javax.management.NotificationBroadcasterSupport; import javax.management.NotificationEmitter; import javax.management.NotificationFilter; import javax.management.NotificationListener; import javax.management.ObjectName; import javax.management.event.EventClient; import javax.management.remote.JMXConnectionNotification; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; /** * A {@link JMXNamespace} that will connect to a remote MBeanServer * by creating a {@link javax.management.remote.JMXConnector} from a * {@link javax.management.remote.JMXServiceURL}. *
* You can call {@link #connect() connect()} and {@link #close close()} * several times. This MBean will emit an {@link AttributeChangeNotification} * when the value of its {@link #isConnected Connected} attribute changes. *
** The JMX Remote Namespace MBean is not connected until {@link * #connect() connect()} is explicitly called. The usual sequence of code to * create a JMX Remote Namespace is thus: *
** final String namespace = "mynamespace"; * final ObjectName name = {@link JMXNamespaces#getNamespaceObjectName * JMXNamespaces.getNamespaceObjectName(namespace)}; * final JMXServiceURL remoteServerURL = .... ; * final Map*optionsMap = .... ; * final MBeanServer masterMBeanServer = .... ; * final JMXRemoteNamespace namespaceMBean = {@link #newJMXRemoteNamespace * JMXRemoteNamespace.newJMXRemoteNamespace(remoteServerURL, optionsMap)}; * masterMBeanServer.registerMBean(namespaceMBean, name); * namespaceMBean.connect(); * // or: masterMBeanServer.invoke(name, {@link #connect() "connect"}, null, null); *
* The JMX Remote Namespace MBean will register for {@linkplain * JMXConnectionNotification JMX Connection Notifications} with its underlying * {@link JMXConnector}. When a JMX Connection Notification indicates that * the underlying connection has failed, the JMX Remote Namespace MBean * closes its underlying connector and switches its {@link #isConnected * Connected} attribute to false, emitting an {@link * AttributeChangeNotification}. *
** At this point, a managing application (or an administrator connected * through a management console) can attempt to reconnect the * JMX Remote Namespace MBean by calling its {@link #connect() connect()} method * again. *
*Note that when the connection with the remote namespace fails, or when * {@link #close} is called, then any notification subscription to * MBeans registered in that namespace will be lost - unless a custom * {@linkplain javax.management.event event service} supporting connection-less * mode was used. *
* @since 1.7 */ public class JMXRemoteNamespace extends JMXNamespace implements JMXRemoteNamespaceMBean, NotificationEmitter { /** * A logger for this class. */ private static final Logger LOG = JmxProperties.NAMESPACE_LOGGER; // This connection listener is used to listen for connection events from // the underlying JMXConnector. It is used in particular to maintain the // "connected" state in this MBean. // private class ConnectionListener implements NotificationListener { private ConnectionListener() { } public void handleNotification(Notification notification, Object handback) { if (!(notification instanceof JMXConnectionNotification)) return; final JMXConnectionNotification cn = (JMXConnectionNotification)notification; final String type = cn.getType(); if (JMXConnectionNotification.CLOSED.equals(type) || JMXConnectionNotification.FAILED.equals(type)) { checkState(this,cn,(JMXConnector)handback); } } } // When the JMXRemoteNamespace is originally created, it is not connected, // which means that the source MBeanServer should be one that throws // exceptions for most methods. When it is subsequently connected, // the methods should be forwarded to the MBeanServerConnection. // We handle this using MBeanServerConnectionWrapper. The // MBeanServerConnection that is supplied to the constructor of // MBeanServerConnectionWrapper is ignored (and in fact it is null) // because the one that is actually used is the one supplied by the // override of getMBeanServerConnection(). private static class JMXRemoteNamespaceDelegate extends MBeanServerConnectionWrapper { private volatile JMXRemoteNamespace parent=null; JMXRemoteNamespaceDelegate() { super(null,null); } @Override public MBeanServerConnection getMBeanServerConnection() { return parent.getMBeanServerConnection(); } @Override public ClassLoader getDefaultClassLoader() { return parent.getDefaultClassLoader(); } // Because this class is instantiated in the super() call from the // constructor of JMXRemoteNamespace, it cannot be an inner class. // This method achieves the effect that an inner class would have // had, of giving the class a reference to the outer "this". synchronized void initParentOnce(JMXRemoteNamespace parent) { if (this.parent != null) throw new UnsupportedOperationException("parent already set"); this.parent=parent; } } private static final MBeanNotificationInfo connectNotification = new MBeanNotificationInfo(new String[] { AttributeChangeNotification.ATTRIBUTE_CHANGE}, "Connected", "Emitted when the Connected state of this object changes"); private static AtomicLong seqNumber = new AtomicLong(0); private final NotificationBroadcasterSupport broadcaster; private final ConnectionListener listener; private final JMXServiceURL jmxURL; private final Map* This constructor is provided for subclasses. * To create a new instance of {@code JMXRemoteNamespace} call * {@link #newJMXRemoteNamespace * JMXRemoteNamespace.newJMXRemoteNamespace(sourceURL, optionsMap)}. *
* @param sourceURL a JMX service URL that can be used to {@linkplain * #connect() connect} to the * source MBean Server. The source MBean Server is the remote * MBean Server which contains the MBeans that will be mirrored * in this namespace. * @param optionsMap the options map that will be passed to the * {@link JMXConnectorFactory} when {@linkplain * JMXConnectorFactory#newJMXConnector creating} the * {@link JMXConnector} used to {@linkplain #connect() connect} * to the remote source MBean Server. Can be null, which is * equivalent to an empty map. * @see #newJMXRemoteNamespace JMXRemoteNamespace.newJMXRemoteNamespace * @see #connect */ protected JMXRemoteNamespace(JMXServiceURL sourceURL, Map* @see #connect * @return The {@code JMXServiceURL} used to connect to the remote * name space. */ public JMXServiceURL getJMXServiceURL() { return jmxURL; } /** * In this class, this method never returns {@code null}, and the * address returned is the {@link #getJMXServiceURL JMXServiceURL} * that is used by this object to {@linkplain #connect} to the remote * name space.
* This behaviour might be overriden by subclasses, if needed.
* For instance, a subclass might want to return {@code null} if it
* doesn't want to expose that JMXServiceURL.
*/
public JMXServiceURL getAddress() {
return getJMXServiceURL();
}
private Map
* The implementation should probably look like:
* This method can be called by subclasses in order to send their own
* notifications.
* In that case, these subclasses might also need to override
* {@link #getNotificationInfo} in order to declare their own
* {@linkplain MBeanNotificationInfo notification types}.
* Creates a new JMXConnector with the specified {@code url} and
* {@code env} options map. The default implementation of this method
* returns {@link JMXConnectorFactory#newJMXConnector
* JMXConnectorFactory.newJMXConnector(jmxURL, env)}. Subclasses can
* override this method to customize behavior. Called when a new connection is established using {@link #connect}
* so that subclasses can customize the connection. The default
* implementation of this method effectively does the following: In other words, it arranges for the client context to be forwarded
* to the remote MBean Server if the remote MBean Server supports contexts;
* otherwise it ignores the client context. A subclass that wanted to narrow into a namespace of
* the remote MBeanServer might look like this: Some connectors may have been designed to work with an earlier
* version of the JMX API, and may not have been upgraded to use
* the {@linkplain javax.management.event Event Service} defined in
* this version of the JMX API. In that case, and if the remote
* server to which this JMXRemoteNamespace connects also contains
* namespaces, it may be necessary to configure explicitly an {@linkplain
* javax.management.event.EventClientDelegate#newForwarder Event Client
* Forwarder} on the remote server side, and to force the use of an {@link
* EventClient} on this client side. A subclass of {@link JMXRemoteNamespace} can provide an
* implementation of {@code getMBeanServerConnection} that will force
* notification subscriptions to flow through an {@link EventClient} over
* a legacy protocol. It can do so by overriding this method in the
* following way:
* Note that the remote server also needs to provide an {@link
* javax.management.event.EventClientDelegateMBean}: configuring only
* the client side (this object) is not enough. In summary, this technique should be used if the remote server
* supports JMX namespaces, but uses a JMX Connector Server whose
* implementation does not transparently use the new Event Service
* (as would be the case with the JMXMPConnectorServer implementation
* from the reference implementation of the JMX Remote API 1.0
* specification). The sequence of events when this method is called includes,
* effectively, the following code: Here, {@code env} is a {@code Map} containing the entries from the
* {@code optionsMap} that was passed to the {@linkplain #JMXRemoteNamespace
* constructor} or to the {@link #newJMXRemoteNamespace newJMXRemoteNamespace}
* factory method. Subclasses can customize connection behavior by overriding the
* {@code getJMXServiceURL}, {@code newJMXConnector}, or
* {@code getMBeanServerConnection} methods.
* final MBeanNotificationInfo[] myOwnNotifs = { .... };
* final MBeanNotificationInfo[] parentNotifs =
* super.getNotificationInfo();
* final Set
*/
public MBeanNotificationInfo[] getNotificationInfo() {
return broadcaster.getNotificationInfo();
}
public void removeNotificationListener(NotificationListener listener)
throws ListenerNotFoundException {
broadcaster.removeNotificationListener(listener);
}
public void removeNotificationListener(NotificationListener listener,
NotificationFilter filter, Object handback)
throws ListenerNotFoundException {
broadcaster.removeNotificationListener(listener, filter, handback);
}
private static long getNextSeqNumber() {
return seqNumber.getAndIncrement();
}
/**
* Sends a notification to registered listeners. Before the notification
* is sent, the following steps are performed:
*
*
* MBeanServerConnection mbsc = {@link JMXConnector#getMBeanServerConnection()
* jmxc.getMBeanServerConnection()};
* try {
* return {@link ClientContext#withDynamicContext
* ClientContext.withDynamicContext(mbsc)};
* } catch (IllegalArgumentException e) {
* return mbsc;
* }
*
*
* Example: connecting to a remote namespace
*
*
* class JMXRemoteSubNamespace extends JMXRemoteNamespace {
* private final String subnamespace;
*
* JMXRemoteSubNamespace(
* JMXServiceURL url, Map{@code
*
* Example: using the Event Service for notifications
*
*
* class JMXRemoteEventClientNamespace extends JMXRemoteNamespace {
* JMXRemoteEventClientNamespace(JMXServiceURL url, {@code Map
*
*
* JMXServiceURL url = {@link #getJMXServiceURL getJMXServiceURL}();
* JMXConnector jmxc = {@link #newJMXConnector newJMXConnector}(url, env);
* jmxc.connect();
* MBeanServerConnection mbsc = {@link #getMBeanServerConnection(JMXConnector)
* getMBeanServerConnection}(jmxc);
*
*
*