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