Skip to content

Instantly share code, notes, and snippets.

@magro
Last active December 16, 2015 04:09
Show Gist options
  • Save magro/5374970 to your computer and use it in GitHub Desktop.
Save magro/5374970 to your computer and use it in GitHub Desktop.
This class allows you to start a jmx connector server in a way that allows to connect from the outside through a firewall: the rmi communication is bound to specific ports. Additionally this solution allows you to start several jvms with the same port configurations, as the rmi registry and the connector server will be bound to a specific ip add…
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.javakaffee.commons.jmx;
import java.io.IOException;
import java.io.Serializable;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.RMISocketFactory;
import java.util.HashMap;
import javax.management.MBeanServer;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnectorServer;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
* This class allows you to start a jmx connector server in a way that allows to
* connect from the outside through a firewall: the rmi communication is bound
* to specific ports.<br/>
* Additionally this solution allows you to start several jvms with the same
* port configurations, as the rmi registry and the connector server will be
* bound to a specific ip address instead of <code>0.0.0.0</code>.
*
* <p>
* When not using a firewall, it's enough to set the following JAVA_OPTS
* environment variable:
*
* <pre>
* JAVA_OPTS="-Dcom.sun.management.jmxremote \
* -Dcom.sun.management.jmxremote.port=8099 \
* -Dcom.sun.management.jmxremote.ssl=false \
* -Dcom.sun.management.jmxremote.authenticate=false
* </pre>
*
* This starts a jmx rmi connector server listening on interface 0.0.0.0 on port 8099.
* The rmi connection then uses dynamic ports for retrieval of stubs.
* </p>
* <p>
* When connecting through a firewall, dynamic ports cannot be used, but a
* static rmi data port is required. The following environment variables are
* used by this class:
* <ul>
* <li>de.javakaffee.jmx.remote: activate remote jmx? true/false
* (default false)</li>
* <li>de.javakaffee.jmx.remote.authenticate: use authentication? true/false
* (default false)</li>
* <li>de.javakaffee.jmx.remote.x.password.file: the password file used for authentication
* (username=password).</li>
* <li>de.javakaffee.jmx.remote.x.access.file: the access file used for authentication
* (username=(readonly|readwrite)).</li>
* <li>de.javakaffee.jmx.remote.hostname: the hostname (ip address / interface) that the
* jmx connector server and the rmi registry are listening on.</li>
* <li>java.rmi.server.hostname: the same as above, this is additionally required so that the
* stubs know where the remote object live. It's required if the rmi server hostname differs from
* what <code>InetAddress.getLocalHost().getHostAddress()</code> returns.</li>
* <li>de.javakaffee.jmx.remote.port: the port that the jmx connector server is
* bound to.</li>
* <li>de.javakaffee.jmx.remote.rmi.server.port: the port that the rmi registry
* is listening on.</li>
* </ul>
* An example for the full set of environment variables is given below:
*
* <pre>
* JAVA_OPTS="$JAVA_OPTS \
* -Dde.javakaffee.jmx.remote=true \
* -Dde.javakaffee.jmx.remote.authenticate=true \
* -Dde.javakaffee.jmx.remote.x.password.file=/tmp/password.properties \
* -Dde.javakaffee.jmx.remote.x.access.file=/tmp/access.properties \
* -Dde.javakaffee.jmx.remote.hostname=some.host.org \
* -Djava.rmi.server.hostname=some.host.org \
* -Dde.javakaffee.jmx.remote.port=8099 \
* -Dde.javakaffee.jmx.remote.rmi.server.port=9099"
* </pre>
*
* </p>
*
* Created on: Sep 1, 2006<br>
*
* @author <a href="mailto:martin.grotzke@javakaffee.de">Martin Grotzke</a>
*/
public final class JmxInitListener implements ServletContextListener {
public static final String JMXREMOTE = "de.javakaffee.jmx.remote";
public static final String AUTHENTICATE = "de.javakaffee.jmx.remote.authenticate";
public static final String PASSWORD_FILE = "de.javakaffee.jmx.remote.x.password.file";
public static final String ACCESS_FILE = "de.javakaffee.jmx.remote.x.access.file";
public static final String JMX_HOSTNAME = "de.javakaffee.jmx.remote.hostname";
public static final String JMX_REMOTE_PORT = "de.javakaffee.jmx.remote.port";
public static final String RMI_REGISTRY_PORT = "de.javakaffee.jmx.remote.rmi.server.port";
private static final String JMX_PASSWORD_FILE = "jmx.remote.x.password.file";
private static final String JMX_ACCESS_FILE = "jmx.remote.x.access.file";
private JMXConnectorServer _cs;
public JmxInitListener() {
}
public void contextInitialized( ServletContextEvent event ) {
initializeJMX();
}
/**
*
* @author Martin Grotzke
*/
private void initializeJMX() {
/*
* first check if jmxremote is defined, otherwise we can quit...
*/
final String jmxRemote = System.getProperty( JMXREMOTE );
if ( !"true".equals( jmxRemote ) ) {
System.out.println( "[INFO] You have defined this " + getClass().getSimpleName() + " in your web.xml"
+ " but not set the jmxremote environment variable " + JMXREMOTE + ","
+ " so i asume that you don't want to have jmx support at all." + "\nOmmitting jmx connector server setup." );
return;
}
System.out.println( "[INFO] " + getClass().getSimpleName() + " starting to initialize jmx..." );
final String defaultHostname = "localhost";
try {
/*
* Get hostname, jmx and rmi port
*/
final String hostname = System.getProperty( JMX_HOSTNAME, defaultHostname );
final String jmxRemotePortString = System.getProperty( JMX_REMOTE_PORT );
final String rmiRegistryPortString = System.getProperty( RMI_REGISTRY_PORT );
if ( isEmpty( jmxRemotePortString ) || isEmpty( rmiRegistryPortString ) ) {
System.out.println( "[WARN] Environment variable for jmx remote port or no rmi registry port missing." +
"\nExpected: " + JMX_REMOTE_PORT + " and " + RMI_REGISTRY_PORT + "." +
"\nOmmitting jmx connector server setup." );
return;
}
final int jmxRemotePort = Integer.parseInt( jmxRemotePortString );
final int rmiRegistryPort = Integer.parseInt( rmiRegistryPortString );
/*
* Create the rmi registry, binding to the correct interface
*/
final InetAddress inetAddress = InetAddress.getByName( hostname );
try {
final InetAddressBoundSocketFactory socketFactory = new InetAddressBoundSocketFactory( inetAddress );
System.out.println( "[INFO] Creating rmi registry, binding to interface " + inetAddress );
LocateRegistry.createRegistry( rmiRegistryPort, null, socketFactory );
} catch ( Exception e ) {
System.out.println( "[WARN] Could not create registry, this might happen when there's already" + " a registry existing on port "
+ rmiRegistryPort + ". We exit here, asuming that this one is set up correctly."
+ "\nThe caught exception is: " + e );
}
final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
final HashMap<String, Object> env = new HashMap<String, Object>();
/*
* authentication requested?
*/
final boolean authenticate = Boolean.parseBoolean( System.getProperty( AUTHENTICATE, Boolean.TRUE.toString() ) );
if ( authenticate ) {
System.out.println( "[INFO] Setting authentication variables..." );
final String passwordFile = System.getProperty( PASSWORD_FILE );
final String accessFile = System.getProperty( ACCESS_FILE );
if ( isEmpty( passwordFile ) )
throw new IllegalArgumentException( "Variable for password file not set (" + PASSWORD_FILE + ")." );
if ( isEmpty( accessFile ) )
throw new IllegalArgumentException( "Variable for access file not set (" + ACCESS_FILE + ")." );
env.put( JMX_PASSWORD_FILE, passwordFile );
env.put( JMX_ACCESS_FILE, accessFile );
}
/*
* Set the interface bound socket factory for the rmi connector server
*/
final InetAddressBoundSocketFactory socketFactory = new InetAddressBoundSocketFactory( inetAddress );
env.put( RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, socketFactory );
/*
* Create the jmx rmi connector server
*/
final String serviceUrl = "service:jmx:rmi://" + hostname + ":" + (jmxRemotePort) +
"/jndi/rmi://" + hostname + ":" + rmiRegistryPort + "/server";
final JMXServiceURL url = new JMXServiceURL( serviceUrl );
System.out.println( "[INFO] Creating jmx connector server with serviceUrl " + serviceUrl );
_cs = JMXConnectorServerFactory.newJMXConnectorServer( url, env, mbs );
_cs.start();
System.out.println( "[INFO] JMX connector server successfully started." );
} catch ( Exception e ) {
System.err.println( "Could not start jmx connector server, continuing startup. Exception thrown: " + e );
e.printStackTrace();
}
}
public static void main( String[] args ) {
System.out.println( "Starting...");
JmxInitListener jmxInitListener = new JmxInitListener();
jmxInitListener.initializeJMX();
synchronized( jmxInitListener ) {
try {
System.out.println( "Waiting, press CTRL-C to stop.");
jmxInitListener.wait();
} catch ( InterruptedException e ) {
System.out.println( "Got interrupted, stopping." );
jmxInitListener.shutdownJMX();
}
}
}
public void contextDestroyed( ServletContextEvent event ) {
shutdownJMX();
}
/**
*
* @author Martin Grotzke
*/
private void shutdownJMX() {
if ( _cs != null ) {
System.out.println( "[INFO] Stopping JMX connector server..." );
try {
_cs.stop();
System.out.println( "[INFO] Successfully stopped JMX connector server." );
} catch ( Exception e ) {
System.err.println( "Could not stop jmx connector server. Exception thrown: " + e );
e.printStackTrace();
}
}
}
private static boolean isEmpty( String value ) {
return value == null || value.trim().length() == 0;
}
static class InetAddressBoundSocketFactory extends RMISocketFactory implements Serializable {
private static final long serialVersionUID = 6890473763854997882L;
private final InetAddress _ipInterface;
InetAddressBoundSocketFactory( final InetAddress ipInterface ) {
_ipInterface = ipInterface;
}
/**
* {@inheritDoc}
*/
@Override
public ServerSocket createServerSocket( int port ) {
try {
return new ServerSocket( port, 50, _ipInterface );
} catch ( IOException e ) {
throw new RuntimeException( "Could not create new ServerSocket on port " + port + " for interface " + _ipInterface + ".", e );
}
}
/**
* {@inheritDoc}
*/
@Override
public Socket createSocket( final String host, int port ) throws IOException {
return new Socket( _ipInterface, port );
}
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
return obj.getClass().equals(getClass());
}
public int hashCode() {
return InetAddressBoundSocketFactory.class.hashCode();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment