diff --git a/src/share/classes/com/sun/jmx/remote/util/EnvHelp.java b/src/share/classes/com/sun/jmx/remote/util/EnvHelp.java index 683359ba950df95d5ddb46fa26dd193081cceff7..e95c1e049df41e837a41cba8e6ed2e64607cc139 100644 --- a/src/share/classes/com/sun/jmx/remote/util/EnvHelp.java +++ b/src/share/classes/com/sun/jmx/remote/util/EnvHelp.java @@ -812,7 +812,7 @@ public class EnvHelp { * @param env * @return */ - public static boolean isServerDaemon(Map env) { + public static boolean isServerDaemon(Map env) { return (env != null) && ("true".equalsIgnoreCase((String)env.get(JMX_SERVER_DAEMON))); } diff --git a/src/share/classes/javax/management/remote/JMXConnectorServer.java b/src/share/classes/javax/management/remote/JMXConnectorServer.java index c9b4721911bd65bcff741b01ab710bd67e78cba6..056114cfc76ca3552028cfea0eff944d54fd4d96 100644 --- a/src/share/classes/javax/management/remote/JMXConnectorServer.java +++ b/src/share/classes/javax/management/remote/JMXConnectorServer.java @@ -386,6 +386,34 @@ public abstract class JMXConnectorServer return false; } + /** + * Closes a client connection. If the connection is successfully closed, + * the method {@link #connectionClosed} is called to notify interested parties. + *

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. + *

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(); + } + /** *

Install {@link MBeanServerForwarder}s in the system chain * based on the attributes in the given {@code Map}. A connector diff --git a/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java b/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java index 9d38c7e89443323e67f219b023bd2adf524b90c9..7acc02adbd5aa861ab5c5d06e6d4419012ccf130 100644 --- a/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java +++ b/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java @@ -602,6 +602,26 @@ public class RMIConnectorServer extends JMXConnectorServer { return true; } + /** + * {@inheritDoc} + *

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. */ diff --git a/src/share/classes/javax/management/remote/rmi/RMIServerImpl.java b/src/share/classes/javax/management/remote/rmi/RMIServerImpl.java index d87b1d0544b7c34aa906fe18e4310dc9fcd41fdb..6fbc9a2b1206ad997a3eb79e20c6e653882a28c3 100644 --- a/src/share/classes/javax/management/remote/rmi/RMIServerImpl.java +++ b/src/share/classes/javax/management/remote/rmi/RMIServerImpl.java @@ -249,20 +249,73 @@ public abstract class RMIServerImpl implements Closeable, RMIServer { RMIConnection client = makeClient(connectionId, subject); - connServer.connectionOpened(connectionId, "Connection opened", null); - dropDeadReferences(); WeakReference wr = new WeakReference(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 ); return client; } + /** + * 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> 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"); + } + /** *

Creates a new client connection. This method is called by * the public method {@link #newClient(Object)}.

diff --git a/test/javax/management/remote/mandatory/connectorServer/CloseConnectionTest.java b/test/javax/management/remote/mandatory/connectorServer/CloseConnectionTest.java new file mode 100644 index 0000000000000000000000000000000000000000..54d05f84273bf60ed7157bd697efaefe0b37dd4f --- /dev/null +++ b/test/javax/management/remote/mandatory/connectorServer/CloseConnectionTest.java @@ -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 received = + new ArrayList(); + } + + 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; +}