4981215: Publishing a port number for management console to access
authorlmalvent
Mon, 10 Mar 2008 23:13:31 +0100
changeset 52 752ebbd838ad
parent 42 6c846a8c97d3
child 53 711cd7c3af02
4981215: Publishing a port number for management console to access Reviewed-by: emcmanus, dfuchs
jdk/src/share/classes/sun/management/ConnectorAddressLink.java
jdk/src/share/classes/sun/management/jmxremote/ConnectorBootstrap.java
jdk/test/sun/management/jmxremote/bootstrap/JvmstatCountersTest.java
--- a/jdk/src/share/classes/sun/management/ConnectorAddressLink.java	Sun Mar 09 21:56:42 2008 -0700
+++ b/jdk/src/share/classes/sun/management/ConnectorAddressLink.java	Mon Mar 10 23:13:31 2008 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright 2004 Sun Microsystems, Inc.  All Rights Reserved.
+ * Copyright 2004-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
@@ -25,12 +25,13 @@
 
 package sun.management;
 
+import java.io.IOException;
 import java.nio.ByteBuffer;
-import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
-import java.util.Iterator;
-import java.util.Set;
-import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import sun.misc.Perf;
 import sun.management.counter.Units;
@@ -46,36 +47,67 @@
 public class ConnectorAddressLink {
 
     private static final String CONNECTOR_ADDRESS_COUNTER =
-        "sun.management.JMXConnectorServer.address";
+            "sun.management.JMXConnectorServer.address";
+
+    /*
+     * The format of the jvmstat counters representing the properties of
+     * a given out-of-the-box JMX remote connector will be as follows:
+     *
+     * sun.management.JMXConnectorServer.<counter>.<key>=<value>
+     *
+     * where:
+     *
+     *     counter = index computed by this class which uniquely identifies
+     *               an out-of-the-box JMX remote connector running in this
+     *               Java virtual machine.
+     *     key/value = a given key/value pair in the map supplied to the
+     *                 exportRemote() method.
+     *
+     * For example,
+     *
+     * sun.management.JMXConnectorServer.0.remoteAddress=service:jmx:rmi:///jndi/rmi://myhost:5000/jmxrmi
+     * sun.management.JMXConnectorServer.0.authenticate=false
+     * sun.management.JMXConnectorServer.0.ssl=false
+     * sun.management.JMXConnectorServer.0.sslRegistry=false
+     * sun.management.JMXConnectorServer.0.sslNeedClientAuth=false
+     */
+    private static final String REMOTE_CONNECTOR_COUNTER_PREFIX =
+            "sun.management.JMXConnectorServer.";
+
+    /*
+     * JMX remote connector counter (it will be incremented every
+     * time a new out-of-the-box JMX remote connector is created).
+     */
+    private static AtomicInteger counter = new AtomicInteger();
 
     /**
      * Exports the specified connector address to the instrumentation buffer
      * so that it can be read by this or other Java virtual machines running
      * on the same system.
      *
-     * @param   address         The connector address.
+     * @param address The connector address.
      */
     public static void export(String address) {
         if (address == null || address.length() == 0) {
             throw new IllegalArgumentException("address not specified");
         }
         Perf perf = Perf.getPerf();
-        perf.createString(CONNECTOR_ADDRESS_COUNTER, 1, Units.STRING.intValue(), address);
+        perf.createString(
+                CONNECTOR_ADDRESS_COUNTER, 1, Units.STRING.intValue(), address);
     }
 
     /**
      * Imports the connector address from the instrument buffer
      * of the specified Java virtual machine.
      *
-     * @param   vmid    an identifier that uniquely identifies a local
-     *                  Java virtual machine, or <code>0</code> to indicate
-     *                  the current Java virtual machine.
+     * @param vmid an identifier that uniquely identifies a local Java virtual
+     * machine, or <code>0</code> to indicate the current Java virtual machine.
      *
-     * @return  the value of the connector address, or <code>null</code>
-     *          if the target VM has not exported a connector address.
+     * @return the value of the connector address, or <code>null</code> if the
+     * target VM has not exported a connector address.
      *
-     * @throws  IOException     An I/O error occurred while trying to acquire
-     *                          the instrumentation buffer.
+     * @throws IOException An I/O error occurred while trying to acquire the
+     * instrumentation buffer.
      */
     public static String importFrom(int vmid) throws IOException {
         Perf perf = Perf.getPerf();
@@ -85,14 +117,65 @@
         } catch (IllegalArgumentException iae) {
             throw new IOException(iae.getMessage());
         }
-        List counters = (new PerfInstrumentation(bb)).findByPattern(CONNECTOR_ADDRESS_COUNTER);
+        List counters =
+                new PerfInstrumentation(bb).findByPattern(CONNECTOR_ADDRESS_COUNTER);
         Iterator i = counters.iterator();
         if (i.hasNext()) {
-            Counter c = (Counter)i.next();
-            return (String)c.getValue();
+            Counter c = (Counter) i.next();
+            return (String) c.getValue();
         } else {
             return null;
         }
     }
 
+    /**
+     * Exports the specified remote connector address and associated
+     * configuration properties to the instrumentation buffer so that
+     * it can be read by this or other Java virtual machines running
+     * on the same system.
+     *
+     * @param properties The remote connector address properties.
+     */
+    public static void exportRemote(Map<String, String> properties) {
+        final int index = counter.getAndIncrement();
+        Perf perf = Perf.getPerf();
+        for (Map.Entry<String, String> entry : properties.entrySet()) {
+            perf.createString(REMOTE_CONNECTOR_COUNTER_PREFIX + index + "." +
+                    entry.getKey(), 1, Units.STRING.intValue(), entry.getValue());
+        }
+    }
+
+    /**
+     * Imports the remote connector address and associated
+     * configuration properties from the instrument buffer
+     * of the specified Java virtual machine.
+     *
+     * @param vmid an identifier that uniquely identifies a local Java virtual
+     * machine, or <code>0</code> to indicate the current Java virtual machine.
+     *
+     * @return a map containing the remote connector's properties, or an empty
+     * map if the target VM has not exported the remote connector's properties.
+     *
+     * @throws IOException An I/O error occurred while trying to acquire the
+     * instrumentation buffer.
+     */
+    public static Map<String, String> importRemoteFrom(int vmid) throws IOException {
+        Perf perf = Perf.getPerf();
+        ByteBuffer bb;
+        try {
+            bb = perf.attach(vmid, "r");
+        } catch (IllegalArgumentException iae) {
+            throw new IOException(iae.getMessage());
+        }
+        List counters = new PerfInstrumentation(bb).getAllCounters();
+        Map<String, String> properties = new HashMap<String, String>();
+        for (Object c : counters) {
+            String name = ((Counter) c).getName();
+            if (name.startsWith(REMOTE_CONNECTOR_COUNTER_PREFIX) &&
+                    !name.equals(CONNECTOR_ADDRESS_COUNTER)) {
+                properties.put(name, ((Counter) c).getValue().toString());
+            }
+        }
+        return properties;
+    }
 }
--- a/jdk/src/share/classes/sun/management/jmxremote/ConnectorBootstrap.java	Sun Mar 09 21:56:42 2008 -0700
+++ b/jdk/src/share/classes/sun/management/jmxremote/ConnectorBootstrap.java	Mon Mar 10 23:13:31 2008 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright 2003-2005 Sun Microsystems, Inc.  All Rights Reserved.
+ * Copyright 2003-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
@@ -39,6 +39,7 @@
 import java.rmi.Remote;
 import java.rmi.RemoteException;
 import java.rmi.registry.Registry;
+import java.rmi.server.RemoteObject;
 import java.rmi.server.RMIClientSocketFactory;
 import java.rmi.server.RMIServerSocketFactory;
 import java.rmi.server.UnicastRemoteObject;
@@ -70,12 +71,14 @@
 
 import javax.security.auth.Subject;
 
+import sun.rmi.server.UnicastRef;
 import sun.rmi.server.UnicastServerRef;
 import sun.rmi.server.UnicastServerRef2;
 
 import sun.management.Agent;
 import sun.management.AgentConfigurationError;
 import static sun.management.AgentConfigurationError.*;
+import sun.management.ConnectorAddressLink;
 import sun.management.FileSystem;
 import sun.management.snmp.util.MibLogger;
 
@@ -92,20 +95,22 @@
      * Default values for JMX configuration properties.
      **/
     public static interface DefaultValues {
-        public static final String PORT="0";
-        public static final String CONFIG_FILE_NAME="management.properties";
-        public static final String USE_SSL="true";
-        public static final String USE_REGISTRY_SSL="false";
-        public static final String USE_AUTHENTICATION="true";
-        public static final String PASSWORD_FILE_NAME="jmxremote.password";
-        public static final String ACCESS_FILE_NAME="jmxremote.access";
-        public static final String SSL_NEED_CLIENT_AUTH="false";
+
+        public static final String PORT = "0";
+        public static final String CONFIG_FILE_NAME = "management.properties";
+        public static final String USE_SSL = "true";
+        public static final String USE_REGISTRY_SSL = "false";
+        public static final String USE_AUTHENTICATION = "true";
+        public static final String PASSWORD_FILE_NAME = "jmxremote.password";
+        public static final String ACCESS_FILE_NAME = "jmxremote.access";
+        public static final String SSL_NEED_CLIENT_AUTH = "false";
     }
 
     /**
      * Names of JMX configuration properties.
      **/
     public static interface PropertyNames {
+
         public static final String PORT =
                 "com.sun.management.jmxremote.port";
         public static final String CONFIG_FILE_NAME =
@@ -133,6 +138,21 @@
     }
 
     /**
+     * JMXConnectorServer associated data.
+     */
+    private static class JMXConnectorServerData {
+
+        public JMXConnectorServerData(
+                JMXConnectorServer jmxConnectorServer,
+                JMXServiceURL jmxRemoteURL) {
+            this.jmxConnectorServer = jmxConnectorServer;
+            this.jmxRemoteURL = jmxRemoteURL;
+        }
+        JMXConnectorServer jmxConnectorServer;
+        JMXServiceURL jmxRemoteURL;
+    }
+
+    /**
      * <p>Prevents our RMI server objects from keeping the JVM alive.</p>
      *
      * <p>We use a private interface in Sun's JMX Remote API implementation
@@ -151,6 +171,7 @@
      * works).  Hence the somewhat misleading name of this class.</p>
      */
     private static class PermanentExporter implements RMIExporter {
+
         public Remote exportObject(Remote obj,
                 int port,
                 RMIClientSocketFactory csf,
@@ -158,24 +179,25 @@
                 throws RemoteException {
 
             synchronized (this) {
-                if (firstExported == null)
+                if (firstExported == null) {
                     firstExported = obj;
+                }
             }
 
             final UnicastServerRef ref;
-            if (csf == null && ssf == null)
+            if (csf == null && ssf == null) {
                 ref = new UnicastServerRef(port);
-            else
+            } else {
                 ref = new UnicastServerRef2(port, csf, ssf);
+            }
             return ref.exportObject(obj, null, true);
         }
 
         // Nothing special to be done for this case
         public boolean unexportObject(Remote obj, boolean force)
-        throws NoSuchObjectException {
+                throws NoSuchObjectException {
             return UnicastRemoteObject.unexportObject(obj, force);
         }
-
         Remote firstExported;
     }
 
@@ -202,19 +224,21 @@
         }
 
         private void checkAccessFileEntries(Subject subject) {
-            if (subject == null)
+            if (subject == null) {
                 throw new SecurityException(
                         "Access denied! No matching entries found in " +
                         "the access file [" + accessFile + "] as the " +
                         "authenticated Subject is null");
+            }
             final Set principals = subject.getPrincipals();
-            for (Iterator i = principals.iterator(); i.hasNext(); ) {
+            for (Iterator i = principals.iterator(); i.hasNext();) {
                 final Principal p = (Principal) i.next();
-                if (properties.containsKey(p.getName()))
+                if (properties.containsKey(p.getName())) {
                     return;
+                }
             }
             final Set<String> principalsStr = new HashSet<String>();
-            for (Iterator i = principals.iterator(); i.hasNext(); ) {
+            for (Iterator i = principals.iterator(); i.hasNext();) {
                 final Principal p = (Principal) i.next();
                 principalsStr.add(p.getName());
             }
@@ -225,16 +249,16 @@
         }
 
         private static Properties propertiesFromFile(String fname)
-        throws IOException {
+                throws IOException {
             Properties p = new Properties();
-            if (fname == null)
+            if (fname == null) {
                 return p;
+            }
             FileInputStream fin = new FileInputStream(fname);
             p.load(fin);
             fin.close();
             return p;
         }
-
         private final Map<String, Object> environment;
         private final Properties properties;
         private final String accessFile;
@@ -251,22 +275,23 @@
 
         // Load a new management properties
         final Properties props = Agent.loadManagementProperties();
-        if (props == null) return null;
+        if (props == null) {
+            return null;
+        }
 
         final String portStr = props.getProperty(PropertyNames.PORT);
 
 
         // System.out.println("initializing: {port=" + portStr + ",
         //                     properties="+props+"}");
-        return initialize(portStr,props);
+        return initialize(portStr, props);
     }
 
     /**
      * Initializes and starts a JMX Connector Server for remote
      * monitoring and management.
      **/
-    public static synchronized
-            JMXConnectorServer initialize(String portStr, Properties props) {
+    public static synchronized JMXConnectorServer initialize(String portStr, Properties props) {
 
         // Get port number
         final int port;
@@ -280,21 +305,21 @@
         }
 
         // Do we use authentication?
-        final String  useAuthenticationStr =
+        final String useAuthenticationStr =
                 props.getProperty(PropertyNames.USE_AUTHENTICATION,
                 DefaultValues.USE_AUTHENTICATION);
         final boolean useAuthentication =
                 Boolean.valueOf(useAuthenticationStr).booleanValue();
 
         // Do we use SSL?
-        final String  useSslStr =
+        final String useSslStr =
                 props.getProperty(PropertyNames.USE_SSL,
                 DefaultValues.USE_SSL);
         final boolean useSsl =
                 Boolean.valueOf(useSslStr).booleanValue();
 
         // Do we use RMI Registry SSL?
-        final String  useRegistrySslStr =
+        final String useRegistrySslStr =
                 props.getProperty(PropertyNames.USE_REGISTRY_SSL,
                 DefaultValues.USE_REGISTRY_SSL);
         final boolean useRegistrySsl =
@@ -307,7 +332,7 @@
             StringTokenizer st = new StringTokenizer(enabledCipherSuites, ",");
             int tokens = st.countTokens();
             enabledCipherSuitesList = new String[tokens];
-            for (int i = 0 ; i < tokens; i++) {
+            for (int i = 0; i < tokens; i++) {
                 enabledCipherSuitesList[i] = st.nextToken();
             }
         }
@@ -319,12 +344,12 @@
             StringTokenizer st = new StringTokenizer(enabledProtocols, ",");
             int tokens = st.countTokens();
             enabledProtocolsList = new String[tokens];
-            for (int i = 0 ; i < tokens; i++) {
+            for (int i = 0; i < tokens; i++) {
                 enabledProtocolsList[i] = st.nextToken();
             }
         }
 
-        final String  sslNeedClientAuthStr =
+        final String sslNeedClientAuthStr =
                 props.getProperty(PropertyNames.SSL_NEED_CLIENT_AUTH,
                 DefaultValues.SSL_NEED_CLIENT_AUTH);
         final boolean sslNeedClientAuth =
@@ -374,39 +399,49 @@
                     sslNeedClientAuth +
                     "\n\t" + PropertyNames.USE_AUTHENTICATION + "=" +
                     useAuthentication +
-                    (useAuthentication ?
-                        (loginConfigName == null ?
-                            ("\n\t" + PropertyNames.PASSWORD_FILE_NAME + "=" +
-                    passwordFileName) :
-                            ("\n\t" + PropertyNames.LOGIN_CONFIG_NAME + "=" +
+                    (useAuthentication ? (loginConfigName == null ? ("\n\t" + PropertyNames.PASSWORD_FILE_NAME + "=" +
+                    passwordFileName) : ("\n\t" + PropertyNames.LOGIN_CONFIG_NAME + "=" +
                     loginConfigName)) : "\n\t" +
                     Agent.getText("jmxremote.ConnectorBootstrap.initialize.noAuthentication")) +
-                    (useAuthentication ?
-                        ("\n\t" + PropertyNames.ACCESS_FILE_NAME + "=" +
+                    (useAuthentication ? ("\n\t" + PropertyNames.ACCESS_FILE_NAME + "=" +
                     accessFileName) : "") +
                     "");
         }
 
         final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
         JMXConnectorServer cs = null;
+        JMXServiceURL url = null;
         try {
-            cs = exportMBeanServer(mbs, port, useSsl, useRegistrySsl,
+            final JMXConnectorServerData data = exportMBeanServer(
+                    mbs, port, useSsl, useRegistrySsl,
                     sslConfigFileName, enabledCipherSuitesList,
                     enabledProtocolsList, sslNeedClientAuth,
                     useAuthentication, loginConfigName,
                     passwordFileName, accessFileName);
-
-            final JMXServiceURL url = cs.getAddress();
+            cs = data.jmxConnectorServer;
+            url = data.jmxRemoteURL;
             log.config("initialize",
                     Agent.getText("jmxremote.ConnectorBootstrap.initialize.ready",
-                    new JMXServiceURL(url.getProtocol(),
-                    url.getHost(),
-                    url.getPort(),
-                    "/jndi/rmi://"+url.getHost()+":"+port+"/"+
-                    "jmxrmi").toString()));
+                    url.toString()));
         } catch (Exception e) {
             throw new AgentConfigurationError(AGENT_EXCEPTION, e, e.toString());
         }
+        try {
+            // Export remote connector address and associated configuration
+            // properties to the instrumentation buffer.
+            Map<String, String> properties = new HashMap<String, String>();
+            properties.put("remoteAddress", url.toString());
+            properties.put("authenticate", useAuthenticationStr);
+            properties.put("ssl", useSslStr);
+            properties.put("sslRegistry", useRegistrySslStr);
+            properties.put("sslNeedClientAuth", sslNeedClientAuthStr);
+            ConnectorAddressLink.exportRemote(properties);
+        } catch (Exception e) {
+            // Remote connector server started but unable to export remote
+            // connector address and associated configuration properties to
+            // the instrumentation buffer - non-fatal error.
+            log.debug("initialize", e);
+        }
         return cs;
     }
 
@@ -452,7 +487,7 @@
     }
 
     private static void checkPasswordFile(String passwordFileName) {
-        if (passwordFileName == null || passwordFileName.length()==0) {
+        if (passwordFileName == null || passwordFileName.length() == 0) {
             throw new AgentConfigurationError(PASSWORD_FILE_NOT_SET);
         }
         File file = new File(passwordFileName);
@@ -468,9 +503,9 @@
         try {
             if (fs.supportsFileSecurity(file)) {
                 if (!fs.isAccessUserOnly(file)) {
-                    final String msg=Agent.getText("jmxremote.ConnectorBootstrap.initialize.password.readonly",
+                    final String msg = Agent.getText("jmxremote.ConnectorBootstrap.initialize.password.readonly",
                             passwordFileName);
-                    log.config("initialize",msg);
+                    log.config("initialize", msg);
                     throw new AgentConfigurationError(PASSWORD_FILE_ACCESS_NOT_RESTRICTED,
                             passwordFileName);
                 }
@@ -482,7 +517,7 @@
     }
 
     private static void checkAccessFile(String accessFileName) {
-        if (accessFileName == null || accessFileName.length()==0) {
+        if (accessFileName == null || accessFileName.length() == 0) {
             throw new AgentConfigurationError(ACCESS_FILE_NOT_SET);
         }
         File file = new File(accessFileName);
@@ -619,7 +654,7 @@
         }
     }
 
-    private static JMXConnectorServer exportMBeanServer(
+    private static JMXConnectorServerData exportMBeanServer(
             MBeanServer mbs,
             int port,
             boolean useSsl,
@@ -697,24 +732,30 @@
         }
 
         final Registry registry;
-        if (useRegistrySsl)
+        if (useRegistrySsl) {
             registry =
                     new SingleEntryRegistry(port, csf, ssf,
                     "jmxrmi", exporter.firstExported);
-        else
+        } else {
             registry =
                     new SingleEntryRegistry(port,
                     "jmxrmi", exporter.firstExported);
+        }
+
+        JMXServiceURL remoteURL = new JMXServiceURL(
+                "service:jmx:rmi:///jndi/rmi://" + url.getHost() + ":" +
+                ((UnicastRef) ((RemoteObject) registry).getRef()).getLiveRef().getPort() +
+                "/jmxrmi");
 
         /* Our exporter remembers the first object it was asked to
-           export, which will be an RMIServerImpl appropriate for
-           publication in our special registry.  We could
-           alternatively have constructed the RMIServerImpl explicitly
-           and then constructed an RMIConnectorServer passing it as a
-           parameter, but that's quite a bit more verbose and pulls in
-           lots of knowledge of the RMI connector.  */
+        export, which will be an RMIServerImpl appropriate for
+        publication in our special registry.  We could
+        alternatively have constructed the RMIServerImpl explicitly
+        and then constructed an RMIConnectorServer passing it as a
+        parameter, but that's quite a bit more verbose and pulls in
+        lots of knowledge of the RMI connector.  */
 
-        return connServer;
+        return new JMXConnectorServerData(connServer, remoteURL);
     }
 
     /**
@@ -726,5 +767,4 @@
     // XXX Revisit: should probably clone this MibLogger....
     private static final MibLogger log =
             new MibLogger(ConnectorBootstrap.class);
-
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/management/jmxremote/bootstrap/JvmstatCountersTest.java	Mon Mar 10 23:13:31 2008 +0100
@@ -0,0 +1,183 @@
+/*
+ * 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 4981215
+ * @summary Tests that the jvmstat counters published by the out-of-the-box
+ *          management agent for the JMX connection details are correct.
+ * @author Luis-Miguel Alventosa
+ * @run clean JvmstatCountersTest
+ * @run build JvmstatCountersTest
+ * @run main/othervm JvmstatCountersTest 1
+ * @run main/othervm -Dcom.sun.management.jmxremote JvmstatCountersTest 2
+ * @run main/othervm -Dcom.sun.management.jmxremote.port=0 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false JvmstatCountersTest 3
+ * @run main/othervm JvmstatCountersTest 4
+ */
+
+import java.io.*;
+import java.lang.management.*;
+import java.util.*;
+import javax.management.*;
+import javax.management.remote.*;
+import com.sun.tools.attach.*;
+import sun.management.ConnectorAddressLink;
+
+public class JvmstatCountersTest {
+
+    public static void checkAddress(String address) throws IOException {
+        System.out.println("Address = " + address);
+        JMXServiceURL url = new JMXServiceURL(address);
+        JMXConnector jmxc = JMXConnectorFactory.connect(url);
+        MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
+        System.out.println("MBean Count = " + mbsc.getMBeanCount());
+    }
+
+    public static void checkKey(Map<String, String> data, int index,
+            String key, String expectedValue) throws Exception {
+        String counter = "sun.management.JMXConnectorServer." + index + "." + key;
+        if (!data.containsKey(counter)) {
+            System.out.println("Test FAILED! Missing counter " + counter);
+            throw new IllegalArgumentException("Test case failed");
+        }
+        String value = data.get(counter);
+        if (key.equals("remoteAddress")) {
+            checkAddress(value);
+        } else if (!expectedValue.equals(value)) {
+            System.out.println("Test FAILED! Invalid counter " +
+                    counter + "=" + value);
+            throw new IllegalArgumentException("Test case failed");
+        }
+        System.out.println("OK: " + counter + "=" + value);
+    }
+
+    public static void main(String args[]) throws Exception {
+        String localAddress = ConnectorAddressLink.importFrom(0);
+        Map<String, String> remoteData = ConnectorAddressLink.importRemoteFrom(0);
+        final int testCase = Integer.parseInt(args[0]);
+        switch (testCase) {
+            case 1:
+                if (localAddress == null && remoteData.isEmpty()) {
+                    System.out.println("Test PASSED! The OOTB management " +
+                            "agent didn't publish any jvmstat counter.");
+                } else {
+                    System.out.println("Test FAILED! The OOTB management " +
+                            "agent unexpectedly published jvmstat counters.");
+                    throw new IllegalArgumentException("Test case 1 failed");
+                }
+                break;
+            case 2:
+                if (localAddress == null) {
+                    System.out.println("Test FAILED! The OOTB management " +
+                            "agent didn't publish the local connector.");
+                    throw new IllegalArgumentException("Test case 2 failed");
+                }
+                checkAddress(localAddress);
+                if (!remoteData.isEmpty()) {
+                    System.out.println("Test FAILED! The OOTB management " +
+                            "agent shouldn't publish the remote connector.");
+                    throw new IllegalArgumentException("Test case 2 failed");
+                }
+                System.out.println("Test PASSED! The OOTB management " +
+                        "agent only publishes the local connector through " +
+                        "a jvmstat counter.");
+                break;
+            case 3:
+                if (localAddress == null) {
+                    System.out.println("Test FAILED! The OOTB management " +
+                            "agent didn't publish the local connector.");
+                    throw new IllegalArgumentException("Test case 3 failed");
+                }
+                checkAddress(localAddress);
+                if (remoteData.isEmpty()) {
+                    System.out.println("Test FAILED! The OOTB management " +
+                            "agent didnn't publish the remote connector.");
+                    throw new IllegalArgumentException("Test case 3 failed");
+                }
+                for (String key : remoteData.keySet()) {
+                    if (!key.startsWith("sun.management.JMXConnectorServer.0.")) {
+                        System.out.println("Test FAILED! The OOTB management " +
+                                "agent shouldn't publish anything which isn't " +
+                                "related to the remote connector.");
+                        throw new IllegalArgumentException("Test case 3 failed");
+                    }
+                }
+                checkKey(remoteData, 0, "remoteAddress", null);
+                checkKey(remoteData, 0, "authenticate", "false");
+                checkKey(remoteData, 0, "ssl", "false");
+                checkKey(remoteData, 0, "sslRegistry", "false");
+                checkKey(remoteData, 0, "sslNeedClientAuth", "false");
+                System.out.println("Test PASSED! The OOTB management " +
+                        "agent publishes both the local and remote " +
+                        "connector info through jvmstat counters.");
+                break;
+            case 4:
+                if (localAddress != null || !remoteData.isEmpty()) {
+                    System.out.println("Test FAILED! The OOTB management " +
+                            "agent unexpectedly published jvmstat counters.");
+                    throw new IllegalArgumentException("Test case 4 failed");
+                }
+                RuntimeMXBean rt = ManagementFactory.getRuntimeMXBean();
+                String name = rt.getName();
+                System.out.println("name = " + name);
+                String vmid = name.substring(0, name.indexOf("@"));
+                System.out.println("vmid = " + vmid);
+                VirtualMachine vm = VirtualMachine.attach(vmid);
+                String agent = vm.getSystemProperties().getProperty("java.home") +
+                        File.separator + "lib" + File.separator + "management-agent.jar";
+                vm.loadAgent(agent, "com.sun.management.jmxremote.port=0,com.sun.management.jmxremote.authenticate=false,com.sun.management.jmxremote.ssl=false");
+                vm.detach();
+                String localAddress2 = ConnectorAddressLink.importFrom(0);
+                if (localAddress2 == null) {
+                    System.out.println("Test FAILED! The OOTB management " +
+                            "agent didn't publish the local connector.");
+                    throw new IllegalArgumentException("Test case 4 failed");
+                }
+                checkAddress(localAddress2);
+                Map<String, String> remoteData2 = ConnectorAddressLink.importRemoteFrom(0);
+                if (remoteData2.isEmpty()) {
+                    System.out.println("Test FAILED! The OOTB management " +
+                            "agent didnn't publish the remote connector.");
+                    throw new IllegalArgumentException("Test case 4 failed");
+                }
+                for (String key : remoteData2.keySet()) {
+                    if (!key.startsWith("sun.management.JMXConnectorServer.0.")) {
+                        System.out.println("Test FAILED! The OOTB management " +
+                                "agent shouldn't publish anything which isn't " +
+                                "related to the remote connector.");
+                        throw new IllegalArgumentException("Test case 4 failed");
+                    }
+                }
+                checkKey(remoteData2, 0, "remoteAddress", null);
+                checkKey(remoteData2, 0, "authenticate", "false");
+                checkKey(remoteData2, 0, "ssl", "false");
+                checkKey(remoteData2, 0, "sslRegistry", "false");
+                checkKey(remoteData2, 0, "sslNeedClientAuth", "false");
+                System.out.println("Test PASSED! The OOTB management agent " +
+                        "publishes both the local and remote connector " +
+                        "info through jvmstat counters when the agent is " +
+                        "loaded through the Attach API.");
+        }
+        System.out.println("Bye! Bye!");
+    }
+}