6332907: Add ability for connector server to close individual connections
Reviewed-by: emcmanus
--- 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;
+}