blob: d54ee19c7772aed34225ef47df0e3d756de4a553 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.synapse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.securevault.PasswordManager;
import org.apache.synapse.securevault.secret.SecretInformation;
import org.apache.synapse.commons.util.RMIRegistryController;
import org.apache.synapse.commons.jmx.JmxInformation;
import org.apache.synapse.commons.jmx.JmxSecretAuthenticator;
import javax.management.MBeanServer;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
import javax.rmi.ssl.SslRMIClientSocketFactory;
import javax.rmi.ssl.SslRMIServerSocketFactory;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
/**
* JMX Adaptor class providing a JMX server connector to be able to query MBeans via JConsole or any
* other JMX-compatible management solution.<br>
* The provided JNDI port will be used to create a local RMI registry. If no RMI port will be
* provided dynamic RMI ports will be used for remote MBean queries.<br>
* If the JMXAdaptor shall be used in a firewalled environment, additionally a fixed RMI port should
* be provided and both ports should be opened in the firewall.<br>
* JMX URL used if only JNDI port is provided:<br>
* <code>service:jmx:rmi:///jndi/rmi://<hostname>:<jndiPort>/synapse</code><br>
* JMX URL used if JNDI port and RMI port are provided:<br>
* <code>service:jmx:rmi://<hostname>:<rmiPort>/jndi/rmi://<hostname>:<jndiPort>/synapse</code><br>
*/
public class JmxAdapter {
/**
* Logger of this class.
*/
private static Log log = LogFactory.getLog(JmxAdapter.class);
/**
* Base port to start with if automatic free port detection is used (default). Configurable in
* synapse.properties via synapse.jmx.jndiPort=0.
*/
private static final int JNDI_AUTO_PORT_OFFSET = 1099;
/**
* Encapsulates all information needed to configure the JMX Adapter.
*/
private JmxInformation jmxInformation;
/**
* @see JMXConnectorServer
*/
private JMXConnectorServer connectorServer;
/**
* Creates a new instance of a JMX Adaptor using the provided JMX information.
*
* @param jmxInformation any JMX related information
*/
public JmxAdapter(JmxInformation jmxInformation) {
this.jmxInformation = jmxInformation;
}
/**
* Lazily creates the RMI registry and starts the JMX connector server based on the
*
* @throws SynapseException if the JMX configuration is erroneous and/or the connector server
* cannot be started
*/
public void start() {
initConfiguration();
try {
boolean registryCreated = false;
int jndiPort = jmxInformation.getJndiPort();
// automatic detection starting at base port
if (jndiPort == 0) {
jndiPort = JNDI_AUTO_PORT_OFFSET;
for (int retries = 0; !registryCreated && (retries < 100); retries++) {
try {
RMIRegistryController.getInstance().createLocalRegistry(jndiPort);
registryCreated = true;
} catch (Exception ignored) {
jndiPort++;
log.warn("Trying alternate port " + jndiPort);
}
}
jmxInformation.setJndiPort(jndiPort);
} else {
RMIRegistryController.getInstance().createLocalRegistry(jndiPort);
registryCreated = true;
}
if (registryCreated) {
jmxInformation.updateJMXUrl();
JMXServiceURL url = new JMXServiceURL(jmxInformation.getJmxUrl());
Map<String, Object> env = createContextMap();
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);
try {
connectorServer.start();
} catch (IOException ex) {
log.warn("Cannot start JMXConnectorServer on " + jmxInformation.getJmxUrl(), ex);
}
}
} catch (Exception ex) {
log.error("Error while setting up remote JMX connector", ex);
}
}
/**
* Stops the JMX connector server.
*/
public void stop() {
if (connectorServer != null) {
log.info("JMXConnectorServer stopping on " + jmxInformation.getJmxUrl());
try {
connectorServer.stop();
RMIRegistryController.getInstance().removeLocalRegistry(jmxInformation.getJndiPort());
jmxInformation = null;
} catch (IOException ex) {
log.error("Error while stopping remote JMX connector", ex);
}
connectorServer = null;
}
}
/**
* Initialized the JMX configuration.
*
* @throws SynapseException if the port or host configuration is erroneous
*/
private void initConfiguration() {
int jndiPort = jmxInformation.getJndiPort();
if ((jndiPort < 0) || (65535 < jndiPort)) {
throw new SynapseException("JNDI Port for Remote Registry not properly configured");
}
int rmiPort = jmxInformation.getRmiPort();
if ((rmiPort < 0) || (65535 < rmiPort)) {
rmiPort = 0;
log.info("No or invalid value specified for JMX RMI port - using dynamic port");
}
String hostname = jmxInformation.getHostName();
if ((hostname == null) || (hostname.trim().length() == 0)) {
try {
InetAddress address = InetAddress.getLocalHost();
jmxInformation.setHostName(address.getHostName());
} catch (UnknownHostException ex) {
throw new SynapseException("Hostname of loopback could not be determined", ex);
}
}
}
/**
* Determines whether the JMX Connector server has been started and is running.
*
* @return true, if the connector server is running, otherwise false
*/
public boolean isRunning() {
return connectorServer != null && connectorServer.isActive();
}
public JmxInformation getJmxInformation() {
return jmxInformation;
}
public void setJmxInformation(JmxInformation jmxInformation) {
this.jmxInformation = jmxInformation;
}
/**
* Creates an environment context map containing the configuration used to start the
* server connector.
*
* @return an environment context map containing the configuration used to start the server
* connector
*/
private Map<String, Object> createContextMap() {
Map<String, Object> env = new HashMap<String, Object>();
if (jmxInformation.isAuthenticate()) {
if (jmxInformation.getRemotePasswordFile() != null) {
env.put("jmx.remote.x.password.file", jmxInformation.getRemotePasswordFile());
} else {
SecretInformation secretInformation = jmxInformation.getSecretInformation();
// Get the global secret resolver
//TODO This should be properly implemented if JMX adapter is going to use out side synapse
PasswordManager pwManager = PasswordManager.getInstance();
if (pwManager.isInitialized()) {
secretInformation.setGlobalSecretResolver(pwManager.getSecretResolver());
}
env.put(JMXConnectorServer.AUTHENTICATOR,
new JmxSecretAuthenticator(jmxInformation.getSecretInformation()));
}
if (jmxInformation.getRemoteAccessFile() != null) {
env.put("jmx.remote.x.access.file", jmxInformation.getRemoteAccessFile());
}
}
if (jmxInformation.isRemoteSSL()) {
log.info("Activated SSL communication");
env.put("jmx.remote.rmi.client.socket.factory", new SslRMIClientSocketFactory());
env.put("jmx.remote.rmi.server.socket.factory", new SslRMIServerSocketFactory());
}
return env;
}
}