001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.camel.management; 018 019 import java.io.IOException; 020 import java.lang.management.ManagementFactory; 021 import java.net.InetAddress; 022 import java.net.UnknownHostException; 023 import java.rmi.RemoteException; 024 import java.rmi.registry.LocateRegistry; 025 import java.util.HashSet; 026 import java.util.List; 027 import java.util.Set; 028 029 import javax.management.InstanceAlreadyExistsException; 030 import javax.management.JMException; 031 import javax.management.MBeanServer; 032 import javax.management.MBeanServerFactory; 033 import javax.management.NotCompliantMBeanException; 034 import javax.management.ObjectInstance; 035 import javax.management.ObjectName; 036 import javax.management.modelmbean.InvalidTargetObjectTypeException; 037 import javax.management.modelmbean.ModelMBeanInfo; 038 import javax.management.modelmbean.RequiredModelMBean; 039 import javax.management.remote.JMXConnectorServer; 040 import javax.management.remote.JMXConnectorServerFactory; 041 import javax.management.remote.JMXServiceURL; 042 043 import org.apache.camel.CamelContext; 044 import org.apache.camel.CamelContextAware; 045 import org.apache.camel.impl.DefaultCamelContext; 046 import org.apache.camel.impl.ServiceSupport; 047 import org.apache.camel.spi.InstrumentationAgent; 048 import org.apache.camel.util.ObjectHelper; 049 import org.apache.commons.logging.Log; 050 import org.apache.commons.logging.LogFactory; 051 import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource; 052 import org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler; 053 054 public class InstrumentationAgentImpl extends ServiceSupport implements InstrumentationAgent, 055 CamelContextAware { 056 public static final String SYSTEM_PROPERTY_JMX = "org.apache.camel.jmx"; 057 public static final String DEFAULT_DOMAIN = "org.apache.camel"; 058 public static final String DEFAULT_HOST = "localhost"; 059 public static final int DEFAULT_PORT = 1099; 060 private static final transient Log LOG = LogFactory.getLog(InstrumentationAgentImpl.class); 061 062 063 private MBeanServer server; 064 private CamelContext context; 065 private Set<ObjectName> mbeans = new HashSet<ObjectName>(); 066 private MetadataMBeanInfoAssembler assembler; 067 private JMXConnectorServer cs; 068 private boolean jmxEnabled; 069 private String jmxDomainName; 070 private int jmxConnectorPort; 071 private CamelNamingStrategy namingStrategy; 072 073 public InstrumentationAgentImpl() { 074 assembler = new MetadataMBeanInfoAssembler(); 075 assembler.setAttributeSource(new AnnotationJmxAttributeSource()); 076 // naming = new 077 // CamelNamingStrategy(agent.getMBeanServer().getDefaultDomain()); 078 namingStrategy = new CamelNamingStrategy(); 079 } 080 081 public CamelContext getCamelContext() { 082 return context; 083 } 084 085 public void setCamelContext(CamelContext camelContext) { 086 context = camelContext; 087 } 088 089 public void setMBeanServer(MBeanServer server) { 090 this.server = server; 091 jmxEnabled = true; 092 } 093 094 public MBeanServer getMBeanServer() { 095 if (server == null) { 096 server = ManagementFactory.getPlatformMBeanServer(); 097 } 098 return server; 099 } 100 101 public void register(Object obj, ObjectName name) throws JMException { 102 register(obj, name, false); 103 } 104 105 public void register(Object obj, ObjectName name, boolean forceRegistration) throws JMException { 106 try { 107 registerMBeanWithServer(obj, name, forceRegistration); 108 } catch (NotCompliantMBeanException e) { 109 // If this is not a "normal" MBean, then try to deploy it using JMX 110 // annotations 111 ModelMBeanInfo mbi = null; 112 mbi = assembler.getMBeanInfo(obj, name.toString()); 113 RequiredModelMBean mbean = (RequiredModelMBean)server.instantiate(RequiredModelMBean.class 114 .getName()); 115 mbean.setModelMBeanInfo(mbi); 116 try { 117 mbean.setManagedResource(obj, "ObjectReference"); 118 } catch (InvalidTargetObjectTypeException itotex) { 119 throw new JMException(itotex.getMessage()); 120 } 121 registerMBeanWithServer(mbean, name, forceRegistration); 122 } 123 } 124 125 public void unregister(ObjectName name) throws JMException { 126 server.unregisterMBean(name); 127 } 128 129 public CamelNamingStrategy getNamingStrategy() { 130 return namingStrategy; 131 } 132 133 public void setNamingStrategy(CamelNamingStrategy namingStrategy) { 134 this.namingStrategy = namingStrategy; 135 } 136 137 protected void doStart() throws Exception { 138 ObjectHelper.notNull(context, "camelContext"); 139 140 if (getMBeanServer() == null) { 141 // The MBeanServer was not injected 142 createMBeanServer(); 143 } 144 145 if (jmxDomainName == null) { 146 jmxDomainName = System.getProperty(SYSTEM_PROPERTY_JMX + ".domain"); 147 if (jmxDomainName == null || jmxDomainName.length() == 0) { 148 jmxDomainName = DEFAULT_DOMAIN; 149 } 150 } 151 configureDomainName(); 152 153 LOG.debug("Starting JMX agent on server: " + getMBeanServer()); 154 155 if (context instanceof DefaultCamelContext) { 156 DefaultCamelContext dc = (DefaultCamelContext)context; 157 InstrumentationLifecycleStrategy ls = new InstrumentationLifecycleStrategy(this); 158 dc.setLifecycleStrategy(ls); 159 ls.onContextCreate(context); 160 } 161 } 162 163 protected void doStop() throws Exception { 164 // Using the array to hold the busMBeans to avoid the 165 // CurrentModificationException 166 Object[] mBeans = mbeans.toArray(); 167 int caught = 0; 168 for (Object name : mBeans) { 169 mbeans.remove((ObjectName)name); 170 try { 171 unregister((ObjectName)name); 172 } catch (JMException jmex) { 173 LOG.info("Exception unregistering MBean", jmex); 174 caught++; 175 } 176 } 177 if (caught > 0) { 178 LOG.warn("A number of " + caught 179 + " exceptions caught while unregistering MBeans during stop operation. " 180 + "See INFO log for details."); 181 } 182 } 183 184 private void registerMBeanWithServer(Object obj, ObjectName name, boolean forceRegistration) 185 throws JMException { 186 187 ObjectInstance instance = null; 188 try { 189 instance = server.registerMBean(obj, name); 190 } catch (InstanceAlreadyExistsException e) { 191 if (forceRegistration) { 192 server.unregisterMBean(name); 193 instance = server.registerMBean(obj, name); 194 } else { 195 throw e; 196 } 197 } 198 199 if (instance != null) { 200 mbeans.add(name); 201 } 202 } 203 204 public void enableJmx(String domainName, int port) { 205 jmxEnabled = true; 206 jmxDomainName = domainName; 207 configureDomainName(); 208 jmxConnectorPort = port; 209 } 210 211 protected void configureDomainName() { 212 if (jmxDomainName != null) { 213 namingStrategy.setDomainName(jmxDomainName); 214 } 215 } 216 217 protected void createMBeanServer() { 218 String hostName = DEFAULT_HOST; 219 boolean canAccessSystemProps = true; 220 try { 221 // we'll do it this way mostly to determine if we should lookup the 222 // hostName 223 SecurityManager sm = System.getSecurityManager(); 224 if (sm != null) { 225 sm.checkPropertiesAccess(); 226 } 227 } catch (SecurityException se) { 228 canAccessSystemProps = false; 229 } 230 231 if (canAccessSystemProps) { 232 if (!jmxEnabled) { 233 jmxEnabled = null != System.getProperty(SYSTEM_PROPERTY_JMX); 234 if (!jmxEnabled) { 235 // we're done here 236 return; 237 } 238 } 239 240 if (jmxConnectorPort <= 0) { 241 String portKey = SYSTEM_PROPERTY_JMX + ".port"; 242 String portValue = System.getProperty(portKey); 243 if (portValue != null && portValue.length() > 0) { 244 try { 245 jmxConnectorPort = Integer.parseInt(portValue); 246 } catch (NumberFormatException nfe) { 247 LOG.info("Invalid port number specified via System property [" + portKey + "=" 248 + portValue + "]. Using default: " + DEFAULT_PORT); 249 jmxConnectorPort = DEFAULT_PORT; 250 } 251 } 252 } 253 254 try { 255 hostName = InetAddress.getLocalHost().getHostName(); 256 } catch (UnknownHostException uhe) { 257 LOG.info("Cannot determine host name. Using default: " + DEFAULT_PORT, uhe); 258 hostName = DEFAULT_HOST; 259 } 260 } else { 261 jmxDomainName = jmxDomainName != null ? jmxDomainName : DEFAULT_DOMAIN; 262 jmxConnectorPort = jmxConnectorPort > 0 ? jmxConnectorPort : DEFAULT_PORT; 263 hostName = DEFAULT_HOST; 264 } 265 266 if (!jmxEnabled) { 267 return; 268 } 269 270 // jmx is enabled but there's no MBeanServer, so create one 271 List servers = MBeanServerFactory.findMBeanServer(jmxDomainName); 272 if (servers.size() == 0) { 273 server = MBeanServerFactory.createMBeanServer(jmxDomainName); 274 } else { 275 server = (MBeanServer)servers.get(0); 276 } 277 278 // we need a connector too 279 try { 280 createJmxConnector(hostName); 281 } catch (IOException ioe) { 282 LOG.warn("Could not create and start jmx connector.", ioe); 283 } 284 } 285 286 protected void createJmxConnector(String host) throws IOException { 287 if (jmxConnectorPort > 0) { 288 try { 289 LocateRegistry.createRegistry(jmxConnectorPort); 290 } catch (RemoteException ex) { 291 // the registry may had been created 292 LocateRegistry.getRegistry(jmxConnectorPort); 293 } 294 295 // Create an RMI connector and start it 296 JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + host + ":" 297 + jmxConnectorPort + "/jmxrmi"); 298 cs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server); 299 300 // Start the connector server asynchronously (in a separate thread). 301 Thread connectorThread = new Thread() { 302 public void run() { 303 try { 304 cs.start(); 305 } catch (IOException ioe) { 306 LOG.warn("Could not start jmx connector thread.", ioe); 307 } 308 } 309 }; 310 connectorThread.setName("JMX Connector Thread [" + url + "]"); 311 connectorThread.start(); 312 LOG.info("Jmx connector thread started on " + url); 313 } 314 } 315 }