6332907: Add ability for connector server to close individual connections
authorsjiang
Tue, 09 Dec 2008 19:44:22 +0100
changeset 1708 4e1939e6e6b5
parent 1707 f6c20f760c59
child 1709 392dd6db361a
child 1710 0bdf9295cab1
6332907: Add ability for connector server to close individual connections Reviewed-by: emcmanus
jdk/src/share/classes/com/sun/jmx/remote/util/EnvHelp.java
jdk/src/share/classes/javax/management/remote/JMXConnectorServer.java
jdk/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java
jdk/src/share/classes/javax/management/remote/rmi/RMIServerImpl.java
jdk/test/javax/management/remote/mandatory/connectorServer/CloseConnectionTest.java
--- a/jdk/src/share/classes/com/sun/jmx/remote/util/EnvHelp.java	Tue Dec 09 18:45:09 2008 +0100
+++ b/jdk/src/share/classes/com/sun/jmx/remote/util/EnvHelp.java	Tue Dec 09 19:44:22 2008 +0100
@@ -812,7 +812,7 @@
      * @param env
      * @return
      */
-    public static boolean isServerDaemon(Map env) {
+    public static boolean isServerDaemon(Map<String, ?> env) {
         return (env != null) &&
                 ("true".equalsIgnoreCase((String)env.get(JMX_SERVER_DAEMON)));
     }
--- a/jdk/src/share/classes/javax/management/remote/JMXConnectorServer.java	Tue Dec 09 18:45:09 2008 +0100
+++ b/jdk/src/share/classes/javax/management/remote/JMXConnectorServer.java	Tue Dec 09 19:44:22 2008 +0100
@@ -387,6 +387,34 @@
     }
 
     /**
+     * Closes a client connection. If the connection is successfully closed,
+     * the method {@link #connectionClosed} is called to notify interested parties.
+     * <P>Not all connector servers support this method. For those that do, it
+     * should be possible to cause a new client connection to fail before it
+     * can be used, by calling this method from within a
+     * {@link javax.management.NotificationListener}
+     * when it receives a {@link JMXConnectionNotification#OPENED} notification.
+     * This allows the owner of a connector server to deny certain connections,
+     * typically based on the information in the connection id.
+     * <P>The implementation of this method in {@code JMXConnectorServer} throws
+     * {@code UnsupportedOperationException}. Subclasses can override this
+     * method to support closing a specified client connection.
+     *
+     * @param connectionId the id of the client connection to be closed.
+     * @throws IllegalStateException if the server is not started or is closed.
+     * @throws IllegalArgumentException if {@code connectionId} is null or is
+     * not the id of any open connection.
+     * @throws java.io.IOException if an I/O error appears when closing the
+     * connection.
+     *
+     * @since 1.7
+     */
+    public void closeConnection(String connectionId)
+            throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * <p>Install {@link MBeanServerForwarder}s in the system chain
      * based on the attributes in the given {@code Map}.  A connector
      * server that {@linkplain #supportsSystemMBeanServerForwarder supports}
--- a/jdk/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java	Tue Dec 09 18:45:09 2008 +0100
+++ b/jdk/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java	Tue Dec 09 19:44:22 2008 +0100
@@ -602,6 +602,26 @@
         return true;
     }
 
+    /**
+     * {@inheritDoc}
+     * <P>The {@code RMIConnectorServer} class does support closing a specified
+     * client connection.
+     * @throws IllegalStateException if the server is not started or is closed.
+     * @throws IllegalArgumentException if {@code connectionId} is null or is
+     * not the id of any open connection.
+     * @since 1.7
+     */
+    @Override
+    public void closeConnection(String connectionId)
+            throws IOException {
+        if (isActive()) {
+            rmiServerImpl.closeConnection(connectionId);
+        } else {
+            throw new IllegalStateException(
+                    "The server is not started or is closed.");
+        }
+    }
+
     /* We repeat the definitions of connection{Opened,Closed,Failed}
        here so that they are accessible to other classes in this package
        even though they have protected access.  */
--- a/jdk/src/share/classes/javax/management/remote/rmi/RMIServerImpl.java	Tue Dec 09 18:45:09 2008 +0100
+++ b/jdk/src/share/classes/javax/management/remote/rmi/RMIServerImpl.java	Tue Dec 09 19:44:22 2008 +0100
@@ -249,14 +249,21 @@
 
         RMIConnection client = makeClient(connectionId, subject);
 
-        connServer.connectionOpened(connectionId, "Connection opened", null);
-
         dropDeadReferences();
         WeakReference<RMIConnection> wr = new WeakReference<RMIConnection>(client);
         synchronized (clientList) {
             clientList.add(wr);
         }
 
+        connServer.connectionOpened(connectionId, "Connection opened", null);
+
+        synchronized (clientList) {
+            if (!clientList.contains(wr)) {
+                // can be removed only by a JMXConnectionNotification listener
+                throw new IOException("The connection is refused.");
+            }
+        }
+
         if (tracing)
             logger.trace("newClient","new connection done: " + connectionId );
 
@@ -264,6 +271,52 @@
     }
 
     /**
+     * Closes a client connection.
+     * @param connectionId the id of the client connection to be closed.
+     * @throws IllegalArgumentException if {@code connectionId} is null or is
+     * not the id of any open connection.
+     * @throws java.io.IOException if an I/O error appears when closing the
+     * connection.
+     *
+     * @since 1.7
+     */
+    public void closeConnection(String connectionId)
+            throws IOException {
+        final boolean debug = logger.debugOn();
+
+        if (debug) logger.trace("closeConnection","cconnectionId="+connectionId);
+
+        if (connectionId == null)
+            throw new IllegalArgumentException("Null connectionId.");
+
+        RMIConnection client = null;
+        synchronized (clientList) {
+            dropDeadReferences();
+            for (Iterator<WeakReference<RMIConnection>> it = clientList.iterator();
+                 it.hasNext(); ) {
+                client = it.next().get();
+                if (client != null && connectionId.equals(client.getConnectionId())) {
+                    it.remove();
+                    break;
+                }
+            }
+        }
+
+        if (client == null) {
+            throw new IllegalArgumentException("Unknown id: "+connectionId);
+        }
+
+        if (debug) logger.trace("closeConnection", "closing client connection.");
+        closeClient(client);
+
+        if (debug) logger.trace("closeConnection", "sending notif");
+        connServer.connectionClosed(connectionId,
+                                    "Client connection closed", null);
+
+        if (debug) logger.trace("closeConnection","done");
+    }
+
+    /**
      * <p>Creates a new client connection.  This method is called by
      * the public method {@link #newClient(Object)}.</p>
      *
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/remote/mandatory/connectorServer/CloseConnectionTest.java	Tue Dec 09 19:44:22 2008 +0100
@@ -0,0 +1,217 @@
+/*
+ * 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
+ * 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 6332907
+ * @summary test the ability for connector server to close individual connections
+ * @author Shanliang JIANG
+ * @run clean CloseConnectionTest
+ * @run build CloseConnectionTest
+ * @run main CloseConnectionTest
+ */
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import javax.management.MBeanServerFactory;
+
+import javax.management.Notification;
+import javax.management.NotificationListener;
+import javax.management.remote.JMXConnectionNotification;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXServiceURL;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+
+public class CloseConnectionTest {
+
+    public static void main(String[] args) throws Exception {
+        System.out.println(">>> Test the ability for connector server to close " +
+                "individual connections.");
+
+        final String[] protos = new String[]{"rmi", "iiop", "jmxmp"};
+        for (String p : protos) {
+            System.out.println("\n>>> Testing the protocol " + p);
+            JMXServiceURL addr = new JMXServiceURL(p, null, 0);
+            System.out.println(">>> Creating a JMXConnectorServer on " + addr);
+            JMXConnectorServer server = null;
+            try {
+                server = JMXConnectorServerFactory.newJMXConnectorServer(addr,
+                        null,
+                        MBeanServerFactory.createMBeanServer());
+            } catch (Exception e) {
+                System.out.println(">>> Skip the protocol: " + p);
+                continue;
+            }
+
+            test1(server);
+            test2(server);
+
+            server.stop();
+        }
+
+        System.out.println(">>> Bye bye!");
+    }
+
+    private static void test1(JMXConnectorServer server) throws Exception {
+        try {
+            server.closeConnection("toto");
+            // not started, known id
+            throw new RuntimeException("An IllegalArgumentException is not thrown.");
+        } catch (IllegalStateException e) {
+            System.out.println(">>> Test1: Got expected IllegalStateException: " + e);
+        }
+
+        server.start();
+        System.out.println(">>>Test1 Started the server on " + server.getAddress());
+
+        try {
+            server.closeConnection("toto");
+            throw new RuntimeException("An IllegalArgumentException is not thrown.");
+        } catch (IllegalArgumentException e) {
+            System.out.println(">> Test1: Got expected IllegalArgumentException: " + e);
+        }
+
+        MyListener listener = new MyListener();
+        server.addNotificationListener(listener, null, null);
+
+        System.out.println(">>> Test1: Connecting a client to the server ...");
+        final JMXConnector conn = JMXConnectorFactory.connect(server.getAddress());
+        conn.getMBeanServerConnection().getDefaultDomain();
+        final String id1 = conn.getConnectionId();
+
+        listener.wait(JMXConnectionNotification.OPENED, timeout);
+
+        System.out.println(">>> Test1: Closing the connection: " + conn.getConnectionId());
+        server.closeConnection(id1);
+        listener.wait(JMXConnectionNotification.CLOSED, timeout);
+
+        System.out.println(">>> Test1: Using again the connector whose connection " +
+                "should be closed by the server, it should reconnect " +
+                "automatically to the server and get a new connection id.");
+        conn.getMBeanServerConnection().getDefaultDomain();
+        final String id2 = conn.getConnectionId();
+        listener.wait(JMXConnectionNotification.OPENED, timeout);
+
+        if (id1.equals(id2)) {
+            throw new RuntimeException("Failed, the first client connection is not closed.");
+        }
+
+        System.out.println(">>> Test1: Greate, we get a new connection id " + id2 +
+                ", the first one is closed as expected.");
+
+        System.out.println(">>> Test1: Closing the client.");
+        conn.close();
+        System.out.println(">>> Test1: Stopping the server.");
+        server.removeNotificationListener(listener);
+    }
+
+    private static void test2(JMXConnectorServer server) throws Exception {
+        System.out.println(">>> Test2 close a connection before " +
+                "the client can use it...");
+        final Killer killer = new Killer(server);
+        server.addNotificationListener(killer, null, null);
+
+        System.out.println(">>> Test2 Connecting a client to the server ...");
+        final JMXConnector conn;
+        try {
+            conn = JMXConnectorFactory.connect(server.getAddress());
+            throw new RuntimeException(">>> Failed, do not receive an " +
+                    "IOException telling the connection is refused.");
+        } catch (IOException ioe) {
+            System.out.println(">>> Test2 got expected IOException: "+ioe);
+        }
+    }
+
+    private static class MyListener implements NotificationListener {
+        public void handleNotification(Notification n, Object hb) {
+            if (n instanceof JMXConnectionNotification) {
+                synchronized (received) {
+                    received.add((JMXConnectionNotification) n);
+                    received.notify();
+                }
+            }
+        }
+
+        public JMXConnectionNotification wait(String type, long timeout)
+                throws Exception {
+            JMXConnectionNotification waited = null;
+            long toWait = timeout;
+            long deadline = System.currentTimeMillis() + timeout;
+            synchronized (received) {
+                while (waited == null && toWait > 0) {
+                    received.wait(toWait);
+                    for (JMXConnectionNotification n : received) {
+                        if (type.equals(n.getType())) {
+                            waited = n;
+                            break;
+                        }
+                    }
+                    received.clear();
+                    toWait = deadline - System.currentTimeMillis();
+                }
+            }
+
+            if (waited == null) {
+                throw new RuntimeException("Do not receive expected notification " + type);
+            } else {
+                System.out.println(">>> Received expected notif: "+type+
+                        " "+waited.getConnectionId());
+            }
+
+            return waited;
+        }
+
+        final List<JMXConnectionNotification> received =
+                new ArrayList<JMXConnectionNotification>();
+    }
+
+    private static class Killer implements NotificationListener {
+        public Killer(JMXConnectorServer server) {
+            this.server = server;
+        }
+        public void handleNotification(Notification n, Object hb) {
+            if (n instanceof JMXConnectionNotification) {
+                if (JMXConnectionNotification.OPENED.equals(n.getType())) {
+                    final JMXConnectionNotification cn =
+                            (JMXConnectionNotification)n;
+                    try {
+                        System.out.println(">>> Killer: close the connection "+
+                                cn.getConnectionId());
+                        server.closeConnection(cn.getConnectionId());
+                    } catch (Exception e) {
+                        // impossible?
+                        e.printStackTrace();
+                        System.exit(1);
+                    }
+                }
+            }
+        }
+
+        private final JMXConnectorServer server;
+    }
+
+    private static final long timeout = 6000;
+}