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.JMException; 030 import javax.management.MBeanServer; 031 import javax.management.MBeanServerFactory; 032 import javax.management.NotCompliantMBeanException; 033 import javax.management.ObjectInstance; 034 import javax.management.ObjectName; 035 import javax.management.modelmbean.InvalidTargetObjectTypeException; 036 import javax.management.modelmbean.ModelMBeanInfo; 037 import javax.management.modelmbean.RequiredModelMBean; 038 import javax.management.remote.JMXConnectorServer; 039 import javax.management.remote.JMXConnectorServerFactory; 040 import javax.management.remote.JMXServiceURL; 041 042 import org.apache.camel.impl.ServiceSupport; 043 import org.apache.camel.spi.InstrumentationAgent; 044 import org.apache.commons.logging.Log; 045 import org.apache.commons.logging.LogFactory; 046 import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource; 047 import org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler; 048 049 /** 050 * Default implementation of the Camel JMX service agent 051 */ 052 public class DefaultInstrumentationAgent extends ServiceSupport implements InstrumentationAgent { 053 054 public static final String DEFAULT_DOMAIN = "org.apache.camel"; 055 public static final String DEFAULT_HOST = "localhost"; 056 public static final int DEFAULT_REGISTRY_PORT = 1099; 057 public static final int DEFAULT_CONNECTION_PORT = -1; 058 public static final String DEFAULT_SERVICE_URL_PATH = "/jmxrmi/camel"; 059 private static final transient Log LOG = LogFactory.getLog(DefaultInstrumentationAgent.class); 060 061 private MBeanServer server; 062 private Set<ObjectName> mbeans = new HashSet<ObjectName>(); 063 private MetadataMBeanInfoAssembler assembler; 064 private JMXConnectorServer cs; 065 066 private Integer registryPort; 067 private Integer connectorPort; 068 private String mBeanServerDefaultDomain; 069 private String mBeanObjectDomainName; 070 private String serviceUrlPath; 071 private Boolean usePlatformMBeanServer = true; 072 private Boolean createConnector; 073 074 protected void finalizeSettings() { 075 if (registryPort == null) { 076 registryPort = Integer.getInteger(JmxSystemPropertyKeys.REGISTRY_PORT, 077 DEFAULT_REGISTRY_PORT); 078 } 079 080 if (connectorPort == null) { 081 connectorPort = Integer.getInteger(JmxSystemPropertyKeys.CONNECTOR_PORT, 082 DEFAULT_CONNECTION_PORT); 083 } 084 085 if (mBeanServerDefaultDomain == null) { 086 mBeanServerDefaultDomain = 087 System.getProperty(JmxSystemPropertyKeys.DOMAIN, DEFAULT_DOMAIN); 088 } 089 090 if (mBeanObjectDomainName == null) { 091 mBeanObjectDomainName = 092 System.getProperty(JmxSystemPropertyKeys.MBEAN_DOMAIN, DEFAULT_DOMAIN); 093 } 094 095 if (serviceUrlPath == null) { 096 serviceUrlPath = 097 System.getProperty(JmxSystemPropertyKeys.SERVICE_URL_PATH, 098 DEFAULT_SERVICE_URL_PATH); 099 } 100 101 if (createConnector == null) { 102 createConnector = Boolean.getBoolean(JmxSystemPropertyKeys.CREATE_CONNECTOR); 103 } 104 105 // "Use platform mbean server" is true by default 106 if (System.getProperty(JmxSystemPropertyKeys.USE_PLATFORM_MBS) != null) { 107 usePlatformMBeanServer = Boolean.getBoolean(JmxSystemPropertyKeys.USE_PLATFORM_MBS); 108 } 109 } 110 111 112 public void setRegistryPort(Integer value) { 113 registryPort = value; 114 } 115 116 public void setConnectorPort(Integer value) { 117 connectorPort = value; 118 } 119 120 public void setMBeanServerDefaultDomain(String value) { 121 mBeanServerDefaultDomain = value; 122 } 123 124 public void setMBeanObjectDomainName(String value) { 125 mBeanObjectDomainName = value; 126 } 127 128 public void setServiceUrlPath(String value) { 129 serviceUrlPath = value; 130 } 131 132 public void setCreateConnector(Boolean flag) { 133 createConnector = flag; 134 } 135 136 public void setUsePlatformMBeanServer(Boolean flag) { 137 usePlatformMBeanServer = flag; 138 } 139 140 public MBeanServer getMBeanServer() { 141 return server; 142 } 143 144 public void register(Object obj, ObjectName name) throws JMException { 145 register(obj, name, false); 146 } 147 148 public void register(Object obj, ObjectName name, boolean forceRegistration) throws JMException { 149 try { 150 registerMBeanWithServer(obj, name, forceRegistration); 151 } catch (NotCompliantMBeanException e) { 152 // If this is not a "normal" MBean, then try to deploy it using JMX 153 // annotations 154 ModelMBeanInfo mbi = null; 155 mbi = assembler.getMBeanInfo(obj, name.toString()); 156 RequiredModelMBean mbean = (RequiredModelMBean)server.instantiate(RequiredModelMBean.class 157 .getName()); 158 mbean.setModelMBeanInfo(mbi); 159 try { 160 mbean.setManagedResource(obj, "ObjectReference"); 161 } catch (InvalidTargetObjectTypeException itotex) { 162 throw new JMException(itotex.getMessage()); 163 } 164 registerMBeanWithServer(mbean, name, forceRegistration); 165 } 166 } 167 168 public void unregister(ObjectName name) throws JMException { 169 server.unregisterMBean(name); 170 } 171 172 protected void doStart() throws Exception { 173 assembler = new MetadataMBeanInfoAssembler(); 174 assembler.setAttributeSource(new AnnotationJmxAttributeSource()); 175 176 // create mbean server if is has not be injected. 177 if (server == null) { 178 finalizeSettings(); 179 createMBeanServer(); 180 } 181 182 if (LOG.isDebugEnabled()) { 183 LOG.debug("Starting JMX agent on server: " + getMBeanServer()); 184 } 185 } 186 187 protected void doStop() throws Exception { 188 // close JMX Connector 189 if (cs != null) { 190 try { 191 cs.stop(); 192 } catch (IOException e) { 193 // ignore 194 } 195 cs = null; 196 } 197 198 // Using the array to hold the busMBeans to avoid the 199 // CurrentModificationException 200 Object[] mBeans = mbeans.toArray(); 201 int caught = 0; 202 for (Object name : mBeans) { 203 mbeans.remove((ObjectName)name); 204 try { 205 unregister((ObjectName)name); 206 } catch (JMException jmex) { 207 LOG.info("Exception unregistering MBean", jmex); 208 caught++; 209 } 210 } 211 if (caught > 0) { 212 LOG.warn("A number of " + caught 213 + " exceptions caught while unregistering MBeans during stop operation." 214 + " See INFO log for details."); 215 } 216 } 217 218 private void registerMBeanWithServer(Object obj, ObjectName name, boolean forceRegistration) 219 throws JMException { 220 221 // have we already registered the bean, there can be shared instances in the camel routes 222 boolean exists = server.isRegistered(name); 223 if (exists) { 224 if (forceRegistration) { 225 LOG.info("ForceRegistration enabled, unregistering existing MBean"); 226 server.unregisterMBean(name); 227 } else { 228 // okay ignore we do not want to force it and it could be a shared instance 229 if (LOG.isDebugEnabled()) { 230 LOG.debug("MBean already registered with objectname: " + name); 231 } 232 } 233 } 234 235 // register bean if by force or not exsists 236 ObjectInstance instance = null; 237 if (forceRegistration || !exists) { 238 if (LOG.isTraceEnabled()) { 239 LOG.trace("Registering MBean with objectname: " + name); 240 } 241 instance = server.registerMBean(obj, name); 242 } 243 244 if (instance != null) { 245 ObjectName registeredName = instance.getObjectName(); 246 if (LOG.isDebugEnabled()) { 247 LOG.debug("Registered MBean with objectname: " + registeredName); 248 } 249 250 mbeans.add(registeredName); 251 } 252 } 253 254 protected void createMBeanServer() { 255 String hostName = DEFAULT_HOST; 256 boolean canAccessSystemProps = true; 257 try { 258 // we'll do it this way mostly to determine if we should lookup the 259 // hostName 260 SecurityManager sm = System.getSecurityManager(); 261 if (sm != null) { 262 sm.checkPropertiesAccess(); 263 } 264 } catch (SecurityException se) { 265 canAccessSystemProps = false; 266 } 267 268 if (canAccessSystemProps) { 269 try { 270 hostName = InetAddress.getLocalHost().getHostName(); 271 } catch (UnknownHostException uhe) { 272 LOG.info("Cannot determine localhost name. Using default: " 273 + DEFAULT_REGISTRY_PORT, uhe); 274 hostName = DEFAULT_HOST; 275 } 276 } else { 277 hostName = DEFAULT_HOST; 278 } 279 280 server = findOrCreateMBeanServer(); 281 282 try { 283 // Create the connector if we need 284 if (createConnector) { 285 createJmxConnector(hostName); 286 } 287 } catch (IOException ioe) { 288 LOG.warn("Could not create and start JMX connector.", ioe); 289 } 290 } 291 292 @SuppressWarnings("unchecked") 293 protected MBeanServer findOrCreateMBeanServer() { 294 295 // return platform mbean server if the option is specified. 296 if (usePlatformMBeanServer) { 297 return ManagementFactory.getPlatformMBeanServer(); 298 } 299 300 // look for the first mbean server that has match default domain name 301 List<MBeanServer> servers = 302 (List<MBeanServer>)MBeanServerFactory.findMBeanServer(null); 303 304 for (MBeanServer server : servers) { 305 if (LOG.isDebugEnabled()) { 306 LOG.debug("Found MBeanServer with default domain " + server.getDefaultDomain()); 307 } 308 309 if (mBeanServerDefaultDomain.equals(server.getDefaultDomain())) { 310 return server; 311 } 312 } 313 314 // create a mbean server with the given default domain name 315 return MBeanServerFactory.createMBeanServer(mBeanServerDefaultDomain); 316 } 317 318 protected void createJmxConnector(String host) throws IOException { 319 try { 320 LocateRegistry.createRegistry(registryPort); 321 if (LOG.isDebugEnabled()) { 322 LOG.debug("Created JMXConnector RMI regisry on port " + registryPort); 323 } 324 } catch (RemoteException ex) { 325 // The registry may had been created, we could get the registry instead 326 } 327 328 // Create an RMI connector and start it 329 JMXServiceURL url; 330 331 if (connectorPort > 0) { 332 url = new JMXServiceURL("service:jmx:rmi://" + host + ":" + connectorPort + "/jndi/rmi://" + host 333 + ":" + registryPort + serviceUrlPath); 334 } else { 335 url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + host + ":" + registryPort 336 + serviceUrlPath); 337 } 338 cs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server); 339 340 // Start the connector server asynchronously (in a separate thread). 341 Thread connectorThread = new Thread() { 342 public void run() { 343 try { 344 cs.start(); 345 } catch (IOException ioe) { 346 LOG.warn("Could not start JMXConnector thread.", ioe); 347 } 348 } 349 }; 350 connectorThread.setName("Camel JMX Connector Thread [" + url + "]"); 351 connectorThread.start(); 352 LOG.info("JMX Connector thread started and listening at: " + url); 353 } 354 355 public String getMBeanObjectDomainName() { 356 return mBeanObjectDomainName; 357 } 358 359 public void setServer(MBeanServer value) { 360 server = value; 361 } 362 363 }