package org.eclipse.jetty.jmx;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.UnknownHostException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.IntConsumer;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnectorServer;
import javax.rmi.ssl.SslRMIClientSocketFactory;
import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.ShutdownThread;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ConnectorServer extends AbstractLifeCycle
{
public static final String RMI_REGISTRY_CLIENT_SOCKET_FACTORY_ATTRIBUTE = "com.sun.jndi.rmi.factory.socket";
private static final Logger LOG = LoggerFactory.getLogger(ConnectorServer.class);
private JMXServiceURL _jmxURL;
private final Map<String, Object> _environment;
private String _objectName;
private SslContextFactory.Server _sslContextFactory;
private int _registryPort;
private int _rmiPort;
private JMXConnectorServer _connectorServer;
private Registry _registry;
public ConnectorServer(JMXServiceURL serviceURL, String objectName)
{
this(serviceURL, null, objectName);
}
public ConnectorServer(JMXServiceURL serviceURL, Map<String, ?> environment, String objectName)
{
this(serviceURL, environment, objectName, null);
}
public ConnectorServer(JMXServiceURL serviceURL, Map<String, ?> environment, String objectName, SslContextFactory.Server sslContextFactory)
{
this._jmxURL = serviceURL;
this._environment = environment == null ? new HashMap<>() : new HashMap<>(environment);
this._objectName = objectName;
this._sslContextFactory = sslContextFactory;
}
public JMXServiceURL getAddress()
{
return _jmxURL;
}
public void putAttribute(String name, Object value)
{
_environment.put(name, value);
}
public String getObjectName()
{
return _objectName;
}
public void setObjectName(String objectName)
{
_objectName = objectName;
}
public SslContextFactory.Server getSslContextFactory()
{
return _sslContextFactory;
}
public void setSslContextFactory(SslContextFactory.Server sslContextFactory)
{
_sslContextFactory = sslContextFactory;
}
@Override
public void doStart() throws Exception
{
boolean rmi = "rmi".equals(_jmxURL.getProtocol());
if (rmi)
{
if (!_environment.containsKey(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE))
_environment.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, new JMXRMIServerSocketFactory(_jmxURL.getHost(), port -> _rmiPort = port));
if (getSslContextFactory() != null)
{
SslRMIClientSocketFactory csf = new SslRMIClientSocketFactory();
if (!_environment.containsKey(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE))
_environment.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf);
if (!_environment.containsKey(RMI_REGISTRY_CLIENT_SOCKET_FACTORY_ATTRIBUTE))
_environment.put(RMI_REGISTRY_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf);
}
}
String urlPath = _jmxURL.getURLPath();
String jndiRMI = "/jndi/rmi://";
if (urlPath.startsWith(jndiRMI))
{
int startIndex = jndiRMI.length();
int endIndex = urlPath.indexOf('/', startIndex);
HostPort hostPort = new HostPort(urlPath.substring(startIndex, endIndex));
String registryHost = startRegistry(hostPort);
if (_registryPort == 0)
_registryPort = hostPort.getPort();
urlPath = jndiRMI + registryHost + ":" + _registryPort + urlPath.substring(endIndex);
_jmxURL = new JMXServiceURL(_jmxURL.getProtocol(), _jmxURL.getHost(), _jmxURL.getPort(), urlPath);
}
MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
_connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(_jmxURL, _environment, mbeanServer);
mbeanServer.registerMBean(_connectorServer, new ObjectName(getObjectName()));
_connectorServer.start();
String rmiHost = normalizeHost(_jmxURL.getHost());
if (_rmiPort == 0)
_rmiPort = _registryPort;
_jmxURL = new JMXServiceURL(_jmxURL.getProtocol(), rmiHost, _rmiPort, urlPath);
ShutdownThread.register(0, this);
LOG.info("JMX URL: {}", _jmxURL);
}
@Override
public void doStop() throws Exception
{
ShutdownThread.deregister(this);
_connectorServer.stop();
MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
mbeanServer.unregisterMBean(new ObjectName(getObjectName()));
stopRegistry();
}
private String startRegistry(HostPort hostPort) throws Exception
{
String host = hostPort.getHost();
int port = hostPort.getPort(1099);
try
{
LocateRegistry.getRegistry(host, port).list();
return normalizeHost(host);
}
catch (Throwable ex)
{
LOG.trace("IGNORED", ex);
}
RMIClientSocketFactory csf = _sslContextFactory == null ? null : new SslRMIClientSocketFactory();
RMIServerSocketFactory ssf = new JMXRMIServerSocketFactory(host, p -> _registryPort = p);
_registry = LocateRegistry.createRegistry(port, csf, ssf);
return normalizeHost(host);
}
private String normalizeHost(String host) throws UnknownHostException
{
return host == null || host.isEmpty() ? InetAddress.getLocalHost().getHostName() : host;
}
private void stopRegistry()
{
if (_registry != null)
{
try
{
UnicastRemoteObject.unexportObject(_registry, true);
}
catch (Exception ex)
{
LOG.trace("IGNORED", ex);
}
finally
{
_registry = null;
}
}
}
private class JMXRMIServerSocketFactory implements RMIServerSocketFactory
{
private final String _host;
private final IntConsumer _portConsumer;
private JMXRMIServerSocketFactory(String host, IntConsumer portConsumer)
{
this._host = host;
this._portConsumer = portConsumer;
}
@Override
public ServerSocket createServerSocket(int port) throws IOException
{
InetAddress address = _host == null || _host.isEmpty() ? InetAddress.getLocalHost() : InetAddress.getByName(_host);
ServerSocket server = createServerSocket(address, port);
_portConsumer.accept(server.getLocalPort());
return server;
}
private ServerSocket createServerSocket(InetAddress address, int port) throws IOException
{
if (_sslContextFactory == null)
{
ServerSocket server = new ServerSocket();
server.setReuseAddress(true);
server.bind(new InetSocketAddress(address, port));
return server;
}
else
{
return _sslContextFactory.newSslServerSocket(address == null ? null : address.getHostName(), port, 0);
}
}
@Override
public int hashCode()
{
return _host != null ? _host.hashCode() : 0;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
JMXRMIServerSocketFactory that = (JMXRMIServerSocketFactory)obj;
return Objects.equals(_host, that._host);
}
}
}