/**
* 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.camel.management;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.management.InstanceAlreadyExistsException;
import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.modelmbean.InvalidTargetObjectTypeException;
import javax.management.modelmbean.ModelMBeanInfo;
import javax.management.modelmbean.RequiredModelMBean;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.impl.ServiceSupport;
import org.apache.camel.spi.InstrumentationAgent;
import org.apache.camel.util.ObjectHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource;
import org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler;
public class InstrumentationAgentImpl extends ServiceSupport implements InstrumentationAgent,
CamelContextAware {
public static final String SYSTEM_PROPERTY_JMX = "org.apache.camel.jmx";
public static final String DEFAULT_DOMAIN = "org.apache.camel";
public static final String DEFAULT_HOST = "localhost";
public static final int DEFAULT_PORT = 1099;
private static final transient Log LOG = LogFactory.getLog(InstrumentationAgentImpl.class);
private MBeanServer server;
private CamelContext context;
private Set<ObjectName> mbeans = new HashSet<ObjectName>();
private MetadataMBeanInfoAssembler assembler;
private JMXConnectorServer cs;
private boolean jmxEnabled;
private String jmxDomainName;
private int jmxConnectorPort;
private CamelNamingStrategy namingStrategy;
public InstrumentationAgentImpl() {
assembler = new MetadataMBeanInfoAssembler();
assembler.setAttributeSource(new AnnotationJmxAttributeSource());
// naming = new
// CamelNamingStrategy(agent.getMBeanServer().getDefaultDomain());
namingStrategy = new CamelNamingStrategy();
}
public CamelContext getCamelContext() {
return context;
}
public void setCamelContext(CamelContext camelContext) {
context = camelContext;
}
public void setMBeanServer(MBeanServer server) {
this.server = server;
jmxEnabled = true;
}
public MBeanServer getMBeanServer() {
if (server == null) {
server = ManagementFactory.getPlatformMBeanServer();
}
return server;
}
public void register(Object obj, ObjectName name) throws JMException {
register(obj, name, false);
}
public void register(Object obj, ObjectName name, boolean forceRegistration) throws JMException {
try {
registerMBeanWithServer(obj, name, forceRegistration);
} catch (NotCompliantMBeanException e) {
// If this is not a "normal" MBean, then try to deploy it using JMX
// annotations
ModelMBeanInfo mbi = null;
mbi = assembler.getMBeanInfo(obj, name.toString());
RequiredModelMBean mbean = (RequiredModelMBean)server.instantiate(RequiredModelMBean.class
.getName());
mbean.setModelMBeanInfo(mbi);
try {
mbean.setManagedResource(obj, "ObjectReference");
} catch (InvalidTargetObjectTypeException itotex) {
throw new JMException(itotex.getMessage());
}
registerMBeanWithServer(mbean, name, forceRegistration);
}
}
public void unregister(ObjectName name) throws JMException {
server.unregisterMBean(name);
}
public CamelNamingStrategy getNamingStrategy() {
return namingStrategy;
}
public void setNamingStrategy(CamelNamingStrategy namingStrategy) {
this.namingStrategy = namingStrategy;
}
protected void doStart() throws Exception {
ObjectHelper.notNull(context, "camelContext");
if (getMBeanServer() == null) {
// The MBeanServer was not injected
createMBeanServer();
}
if (jmxDomainName == null) {
jmxDomainName = System.getProperty(SYSTEM_PROPERTY_JMX + ".domain");
if (jmxDomainName == null || jmxDomainName.length() == 0) {
jmxDomainName = DEFAULT_DOMAIN;
}
}
configureDomainName();
LOG.debug("Starting JMX agent on server: " + getMBeanServer());
if (context instanceof DefaultCamelContext) {
DefaultCamelContext dc = (DefaultCamelContext)context;
InstrumentationLifecycleStrategy ls = new InstrumentationLifecycleStrategy(this);
dc.setLifecycleStrategy(ls);
ls.onContextCreate(context);
}
}
protected void doStop() throws Exception {
// Using the array to hold the busMBeans to avoid the
// CurrentModificationException
Object[] mBeans = mbeans.toArray();
int caught = 0;
for (Object name : mBeans) {
mbeans.remove((ObjectName)name);
try {
unregister((ObjectName)name);
} catch (JMException jmex) {
LOG.info("Exception unregistering MBean", jmex);
caught++;
}
}
if (caught > 0) {
LOG.warn("A number of " + caught
+ " exceptions caught while unregistering MBeans during stop operation. "
+ "See INFO log for details.");
}
}
private void registerMBeanWithServer(Object obj, ObjectName name, boolean forceRegistration)
throws JMException {
ObjectInstance instance = null;
try {
instance = server.registerMBean(obj, name);
} catch (InstanceAlreadyExistsException e) {
if (forceRegistration) {
server.unregisterMBean(name);
instance = server.registerMBean(obj, name);
} else {
throw e;
}
}
if (instance != null) {
mbeans.add(name);
}
}
public void enableJmx(String domainName, int port) {
jmxEnabled = true;
jmxDomainName = domainName;
configureDomainName();
jmxConnectorPort = port;
}
protected void configureDomainName() {
if (jmxDomainName != null) {
namingStrategy.setDomainName(jmxDomainName);
}
}
protected void createMBeanServer() {
String hostName = DEFAULT_HOST;
boolean canAccessSystemProps = true;
try {
// we'll do it this way mostly to determine if we should lookup the
// hostName
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPropertiesAccess();
}
} catch (SecurityException se) {
canAccessSystemProps = false;
}
if (canAccessSystemProps) {
if (!jmxEnabled) {
jmxEnabled = null != System.getProperty(SYSTEM_PROPERTY_JMX);
if (!jmxEnabled) {
// we're done here
return;
}
}
if (jmxConnectorPort <= 0) {
String portKey = SYSTEM_PROPERTY_JMX + ".port";
String portValue = System.getProperty(portKey);
if (portValue != null && portValue.length() > 0) {
try {
jmxConnectorPort = Integer.parseInt(portValue);
} catch (NumberFormatException nfe) {
LOG.info("Invalid port number specified via System property [" + portKey + "="
+ portValue + "]. Using default: " + DEFAULT_PORT);
jmxConnectorPort = DEFAULT_PORT;
}
}
}
try {
hostName = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException uhe) {
LOG.info("Cannot determine host name. Using default: " + DEFAULT_PORT, uhe);
hostName = DEFAULT_HOST;
}
} else {
jmxDomainName = jmxDomainName != null ? jmxDomainName : DEFAULT_DOMAIN;
jmxConnectorPort = jmxConnectorPort > 0 ? jmxConnectorPort : DEFAULT_PORT;
hostName = DEFAULT_HOST;
}
if (!jmxEnabled) {
return;
}
// jmx is enabled but there's no MBeanServer, so create one
List servers = MBeanServerFactory.findMBeanServer(jmxDomainName);
if (servers.size() == 0) {
server = MBeanServerFactory.createMBeanServer(jmxDomainName);
} else {
server = (MBeanServer)servers.get(0);
}
// we need a connector too
try {
createJmxConnector(hostName);
} catch (IOException ioe) {
LOG.warn("Could not create and start jmx connector.", ioe);
}
}
protected void createJmxConnector(String host) throws IOException {
if (jmxConnectorPort > 0) {
try {
LocateRegistry.createRegistry(jmxConnectorPort);
} catch (RemoteException ex) {
// the registry may had been created
LocateRegistry.getRegistry(jmxConnectorPort);
}
// Create an RMI connector and start it
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + host + ":"
+ jmxConnectorPort + "/jmxrmi");
cs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server);
// Start the connector server asynchronously (in a separate thread).
Thread connectorThread = new Thread() {
public void run() {
try {
cs.start();
} catch (IOException ioe) {
LOG.warn("Could not start jmx connector thread.", ioe);
}
}
};
connectorThread.setName("JMX Connector Thread [" + url + "]");
connectorThread.start();
LOG.info("Jmx connector thread started on " + url);
}
}
}