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 */ 017package org.apache.activemq.broker.jmx; 018 019import java.io.IOException; 020import java.lang.reflect.Method; 021import java.rmi.NoSuchObjectException; 022import java.rmi.registry.LocateRegistry; 023import java.rmi.registry.Registry; 024import java.rmi.server.UnicastRemoteObject; 025import java.util.List; 026import java.util.Map; 027import java.util.Set; 028import java.util.concurrent.ConcurrentHashMap; 029import java.util.concurrent.atomic.AtomicBoolean; 030 031import javax.management.Attribute; 032import javax.management.InstanceNotFoundException; 033import javax.management.JMException; 034import javax.management.MBeanServer; 035import javax.management.MBeanServerFactory; 036import javax.management.MBeanServerInvocationHandler; 037import javax.management.MalformedObjectNameException; 038import javax.management.ObjectInstance; 039import javax.management.ObjectName; 040import javax.management.QueryExp; 041import javax.management.remote.JMXConnectorServer; 042import javax.management.remote.JMXConnectorServerFactory; 043import javax.management.remote.JMXServiceURL; 044 045import org.apache.activemq.Service; 046import org.slf4j.Logger; 047import org.slf4j.LoggerFactory; 048import org.slf4j.MDC; 049 050/** 051 * An abstraction over JMX mbean registration 052 * 053 * @org.apache.xbean.XBean 054 * 055 */ 056public class ManagementContext implements Service { 057 058 /** 059 * Default activemq domain 060 */ 061 public static final String DEFAULT_DOMAIN = "org.apache.activemq"; 062 063 private static final Logger LOG = LoggerFactory.getLogger(ManagementContext.class); 064 private MBeanServer beanServer; 065 private String jmxDomainName = DEFAULT_DOMAIN; 066 private boolean useMBeanServer = true; 067 private boolean createMBeanServer = true; 068 private boolean locallyCreateMBeanServer; 069 private boolean createConnector = true; 070 private boolean findTigerMbeanServer = true; 071 private String connectorHost = "localhost"; 072 private int connectorPort = 1099; 073 private Map<String, ?> environment; 074 private int rmiServerPort; 075 private String connectorPath = "/jmxrmi"; 076 private final AtomicBoolean started = new AtomicBoolean(false); 077 private final AtomicBoolean connectorStarting = new AtomicBoolean(false); 078 private JMXConnectorServer connectorServer; 079 private ObjectName namingServiceObjectName; 080 private Registry registry; 081 private final Map<ObjectName, ObjectName> registeredMBeanNames = new ConcurrentHashMap<ObjectName, ObjectName>(); 082 private boolean allowRemoteAddressInMBeanNames = true; 083 private String brokerName; 084 085 public ManagementContext() { 086 this(null); 087 } 088 089 public ManagementContext(MBeanServer server) { 090 this.beanServer = server; 091 } 092 093 @Override 094 public void start() throws IOException { 095 // lets force the MBeanServer to be created if needed 096 if (started.compareAndSet(false, true)) { 097 098 // fallback and use localhost 099 if (connectorHost == null) { 100 connectorHost = "localhost"; 101 } 102 103 // force mbean server to be looked up, so we have it 104 getMBeanServer(); 105 106 if (connectorServer != null) { 107 try { 108 if (getMBeanServer().isRegistered(namingServiceObjectName)) { 109 LOG.debug("Invoking start on mbean: {}", namingServiceObjectName); 110 getMBeanServer().invoke(namingServiceObjectName, "start", null, null); 111 } 112 } catch (Throwable ignore) { 113 LOG.debug("Error invoking start on MBean {}. This exception is ignored.", namingServiceObjectName, ignore); 114 } 115 116 Thread t = new Thread("JMX connector") { 117 @Override 118 public void run() { 119 // ensure we use MDC logging with the broker name, so people can see the logs if MDC was in use 120 if (brokerName != null) { 121 MDC.put("activemq.broker", brokerName); 122 } 123 try { 124 JMXConnectorServer server = connectorServer; 125 if (started.get() && server != null) { 126 LOG.debug("Starting JMXConnectorServer..."); 127 connectorStarting.set(true); 128 try { 129 // need to remove MDC as we must not inherit MDC in child threads causing leaks 130 MDC.remove("activemq.broker"); 131 server.start(); 132 } finally { 133 if (brokerName != null) { 134 MDC.put("activemq.broker", brokerName); 135 } 136 connectorStarting.set(false); 137 } 138 LOG.info("JMX consoles can connect to {}", server.getAddress()); 139 } 140 } catch (IOException e) { 141 LOG.warn("Failed to start JMX connector {}. Will restart management to re-create JMX connector, trying to remedy this issue.", e.getMessage()); 142 LOG.debug("Reason for failed JMX connector start", e); 143 } finally { 144 MDC.remove("activemq.broker"); 145 } 146 } 147 }; 148 t.setDaemon(true); 149 t.start(); 150 } 151 } 152 } 153 154 @Override 155 public void stop() throws Exception { 156 if (started.compareAndSet(true, false)) { 157 MBeanServer mbeanServer = getMBeanServer(); 158 159 // unregister the mbeans we have registered 160 if (mbeanServer != null) { 161 for (Map.Entry<ObjectName, ObjectName> entry : registeredMBeanNames.entrySet()) { 162 ObjectName actualName = entry.getValue(); 163 if (actualName != null && beanServer.isRegistered(actualName)) { 164 LOG.debug("Unregistering MBean {}", actualName); 165 mbeanServer.unregisterMBean(actualName); 166 } 167 } 168 } 169 registeredMBeanNames.clear(); 170 171 JMXConnectorServer server = connectorServer; 172 connectorServer = null; 173 if (server != null) { 174 try { 175 if (!connectorStarting.get()) { 176 LOG.debug("Stopping jmx connector"); 177 server.stop(); 178 } 179 } catch (IOException e) { 180 LOG.warn("Failed to stop jmx connector: {}", e.getMessage()); 181 } 182 // stop naming service mbean 183 try { 184 if (namingServiceObjectName != null && getMBeanServer().isRegistered(namingServiceObjectName)) { 185 LOG.debug("Stopping MBean {}", namingServiceObjectName); 186 getMBeanServer().invoke(namingServiceObjectName, "stop", null, null); 187 LOG.debug("Unregistering MBean {}", namingServiceObjectName); 188 getMBeanServer().unregisterMBean(namingServiceObjectName); 189 } 190 } catch (Throwable ignore) { 191 LOG.warn("Error stopping and unregsitering MBean {} due to {}", namingServiceObjectName, ignore.getMessage()); 192 } 193 namingServiceObjectName = null; 194 } 195 196 if (locallyCreateMBeanServer && beanServer != null) { 197 // check to see if the factory knows about this server 198 List<MBeanServer> list = MBeanServerFactory.findMBeanServer(null); 199 if (list != null && !list.isEmpty() && list.contains(beanServer)) { 200 LOG.debug("Releasing MBeanServer {}", beanServer); 201 MBeanServerFactory.releaseMBeanServer(beanServer); 202 } 203 } 204 beanServer = null; 205 } 206 207 // Un-export JMX RMI registry, if it was created 208 if (registry != null) { 209 try { 210 UnicastRemoteObject.unexportObject(registry, true); 211 LOG.debug("Unexported JMX RMI Registry"); 212 } catch (NoSuchObjectException e) { 213 LOG.debug("Error occurred while unexporting JMX RMI registry. This exception will be ignored."); 214 } 215 216 registry = null; 217 } 218 } 219 220 /** 221 * Gets the broker name this context is used by, may be <tt>null</tt> 222 * if the broker name was not set. 223 */ 224 public String getBrokerName() { 225 return brokerName; 226 } 227 228 /** 229 * Sets the broker name this context is being used by. 230 */ 231 public void setBrokerName(String brokerName) { 232 this.brokerName = brokerName; 233 } 234 235 /** 236 * @return Returns the jmxDomainName. 237 */ 238 public String getJmxDomainName() { 239 return jmxDomainName; 240 } 241 242 /** 243 * @param jmxDomainName The jmxDomainName to set. 244 */ 245 public void setJmxDomainName(String jmxDomainName) { 246 this.jmxDomainName = jmxDomainName; 247 } 248 249 /** 250 * Get the MBeanServer 251 * 252 * @return the MBeanServer 253 */ 254 protected MBeanServer getMBeanServer() { 255 if (this.beanServer == null) { 256 this.beanServer = findMBeanServer(); 257 } 258 return beanServer; 259 } 260 261 /** 262 * Set the MBeanServer 263 * 264 * @param beanServer 265 */ 266 public void setMBeanServer(MBeanServer beanServer) { 267 this.beanServer = beanServer; 268 } 269 270 /** 271 * @return Returns the useMBeanServer. 272 */ 273 public boolean isUseMBeanServer() { 274 return useMBeanServer; 275 } 276 277 /** 278 * @param useMBeanServer The useMBeanServer to set. 279 */ 280 public void setUseMBeanServer(boolean useMBeanServer) { 281 this.useMBeanServer = useMBeanServer; 282 } 283 284 /** 285 * @return Returns the createMBeanServer flag. 286 */ 287 public boolean isCreateMBeanServer() { 288 return createMBeanServer; 289 } 290 291 /** 292 * @param enableJMX Set createMBeanServer. 293 */ 294 public void setCreateMBeanServer(boolean enableJMX) { 295 this.createMBeanServer = enableJMX; 296 } 297 298 public boolean isFindTigerMbeanServer() { 299 return findTigerMbeanServer; 300 } 301 302 public boolean isConnectorStarted() { 303 return connectorStarting.get() || (connectorServer != null && connectorServer.isActive()); 304 } 305 306 /** 307 * Enables/disables the searching for the Java 5 platform MBeanServer 308 */ 309 public void setFindTigerMbeanServer(boolean findTigerMbeanServer) { 310 this.findTigerMbeanServer = findTigerMbeanServer; 311 } 312 313 /** 314 * Formulate and return the MBean ObjectName of a custom control MBean 315 * 316 * @param type 317 * @param name 318 * @return the JMX ObjectName of the MBean, or <code>null</code> if 319 * <code>customName</code> is invalid. 320 */ 321 public ObjectName createCustomComponentMBeanName(String type, String name) { 322 ObjectName result = null; 323 String tmp = jmxDomainName + ":" + "type=" + sanitizeString(type) + ",name=" + sanitizeString(name); 324 try { 325 result = new ObjectName(tmp); 326 } catch (MalformedObjectNameException e) { 327 LOG.error("Couldn't create ObjectName from: {}, {}", type, name); 328 } 329 return result; 330 } 331 332 /** 333 * The ':' and '/' characters are reserved in ObjectNames 334 * 335 * @param in 336 * @return sanitized String 337 */ 338 private static String sanitizeString(String in) { 339 String result = null; 340 if (in != null) { 341 result = in.replace(':', '_'); 342 result = result.replace('/', '_'); 343 result = result.replace('\\', '_'); 344 } 345 return result; 346 } 347 348 /** 349 * Retrieve an System ObjectName 350 * 351 * @param domainName 352 * @param containerName 353 * @param theClass 354 * @return the ObjectName 355 * @throws MalformedObjectNameException 356 */ 357 public static ObjectName getSystemObjectName(String domainName, String containerName, Class<?> theClass) throws MalformedObjectNameException, NullPointerException { 358 String tmp = domainName + ":" + "type=" + theClass.getName() + ",name=" + getRelativeName(containerName, theClass); 359 return new ObjectName(tmp); 360 } 361 362 private static String getRelativeName(String containerName, Class<?> theClass) { 363 String name = theClass.getName(); 364 int index = name.lastIndexOf("."); 365 if (index >= 0 && (index + 1) < name.length()) { 366 name = name.substring(index + 1); 367 } 368 return containerName + "." + name; 369 } 370 371 public Object newProxyInstance(ObjectName objectName, Class<?> interfaceClass, boolean notificationBroadcaster){ 372 return MBeanServerInvocationHandler.newProxyInstance(getMBeanServer(), objectName, interfaceClass, notificationBroadcaster); 373 } 374 375 public Object getAttribute(ObjectName name, String attribute) throws Exception{ 376 return getMBeanServer().getAttribute(name, attribute); 377 } 378 379 public ObjectInstance registerMBean(Object bean, ObjectName name) throws Exception{ 380 ObjectInstance result = getMBeanServer().registerMBean(bean, name); 381 this.registeredMBeanNames.put(name, result.getObjectName()); 382 return result; 383 } 384 385 public Set<ObjectName> queryNames(ObjectName name, QueryExp query) throws Exception{ 386 if (name != null) { 387 ObjectName actualName = this.registeredMBeanNames.get(name); 388 if (actualName != null) { 389 return getMBeanServer().queryNames(actualName, query); 390 } 391 } 392 return getMBeanServer().queryNames(name, query); 393 } 394 395 public ObjectInstance getObjectInstance(ObjectName name) throws InstanceNotFoundException { 396 return getMBeanServer().getObjectInstance(name); 397 } 398 399 /** 400 * Unregister an MBean 401 * 402 * @param name 403 * @throws JMException 404 */ 405 public void unregisterMBean(ObjectName name) throws JMException { 406 ObjectName actualName = this.registeredMBeanNames.get(name); 407 if (beanServer != null && actualName != null && beanServer.isRegistered(actualName) && this.registeredMBeanNames.remove(name) != null) { 408 LOG.debug("Unregistering MBean {}", actualName); 409 beanServer.unregisterMBean(actualName); 410 } 411 } 412 413 protected synchronized MBeanServer findMBeanServer() { 414 MBeanServer result = null; 415 416 try { 417 if (useMBeanServer) { 418 if (findTigerMbeanServer) { 419 result = findTigerMBeanServer(); 420 } 421 if (result == null) { 422 // lets piggy back on another MBeanServer - we could be in an appserver! 423 List<MBeanServer> list = MBeanServerFactory.findMBeanServer(null); 424 if (list != null && list.size() > 0) { 425 result = list.get(0); 426 } 427 } 428 } 429 if (result == null && createMBeanServer) { 430 result = createMBeanServer(); 431 } 432 } catch (NoClassDefFoundError e) { 433 LOG.error("Could not load MBeanServer", e); 434 } catch (Throwable e) { 435 // probably don't have access to system properties 436 LOG.error("Failed to initialize MBeanServer", e); 437 } 438 return result; 439 } 440 441 public MBeanServer findTigerMBeanServer() { 442 String name = "java.lang.management.ManagementFactory"; 443 Class<?> type = loadClass(name, ManagementContext.class.getClassLoader()); 444 if (type != null) { 445 try { 446 Method method = type.getMethod("getPlatformMBeanServer", new Class[0]); 447 if (method != null) { 448 Object answer = method.invoke(null, new Object[0]); 449 if (answer instanceof MBeanServer) { 450 if (createConnector) { 451 createConnector((MBeanServer)answer); 452 } 453 return (MBeanServer)answer; 454 } else { 455 LOG.warn("Could not cast: {} into an MBeanServer. There must be some classloader strangeness in town", answer); 456 } 457 } else { 458 LOG.warn("Method getPlatformMBeanServer() does not appear visible on type: {}", type.getName()); 459 } 460 } catch (Exception e) { 461 LOG.warn("Failed to call getPlatformMBeanServer() due to: ", e); 462 } 463 } else { 464 LOG.trace("Class not found: {} so probably running on Java 1.4", name); 465 } 466 return null; 467 } 468 469 private static Class<?> loadClass(String name, ClassLoader loader) { 470 try { 471 return loader.loadClass(name); 472 } catch (ClassNotFoundException e) { 473 try { 474 return Thread.currentThread().getContextClassLoader().loadClass(name); 475 } catch (ClassNotFoundException e1) { 476 return null; 477 } 478 } 479 } 480 481 /** 482 * @return 483 * @throws NullPointerException 484 * @throws MalformedObjectNameException 485 * @throws IOException 486 */ 487 protected MBeanServer createMBeanServer() throws MalformedObjectNameException, IOException { 488 MBeanServer mbeanServer = MBeanServerFactory.createMBeanServer(jmxDomainName); 489 locallyCreateMBeanServer = true; 490 if (createConnector) { 491 createConnector(mbeanServer); 492 } 493 return mbeanServer; 494 } 495 496 /** 497 * @param mbeanServer 498 * @throws MalformedObjectNameException 499 * @throws IOException 500 */ 501 private void createConnector(MBeanServer mbeanServer) throws MalformedObjectNameException, IOException { 502 // Create the NamingService, needed by JSR 160 503 try { 504 if (registry == null) { 505 LOG.debug("Creating RMIRegistry on port {}", connectorPort); 506 registry = LocateRegistry.createRegistry(connectorPort); 507 } 508 namingServiceObjectName = ObjectName.getInstance("naming:type=rmiregistry"); 509 510 // Do not use the createMBean as the mx4j jar may not be in the 511 // same class loader than the server 512 Class<?> cl = Class.forName("mx4j.tools.naming.NamingService"); 513 mbeanServer.registerMBean(cl.newInstance(), namingServiceObjectName); 514 515 // set the naming port 516 Attribute attr = new Attribute("Port", Integer.valueOf(connectorPort)); 517 mbeanServer.setAttribute(namingServiceObjectName, attr); 518 } catch(ClassNotFoundException e) { 519 LOG.debug("Probably not using JRE 1.4: {}", e.getLocalizedMessage()); 520 } catch (Throwable e) { 521 LOG.debug("Failed to create local registry. This exception will be ignored.", e); 522 } 523 524 // Create the JMXConnectorServer 525 String rmiServer = ""; 526 if (rmiServerPort != 0) { 527 // This is handy to use if you have a firewall and need to force JMX to use fixed ports. 528 rmiServer = ""+getConnectorHost()+":" + rmiServerPort; 529 } 530 String serviceURL = "service:jmx:rmi://" + rmiServer + "/jndi/rmi://" +getConnectorHost()+":" + connectorPort + connectorPath; 531 JMXServiceURL url = new JMXServiceURL(serviceURL); 532 connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, environment, mbeanServer); 533 534 LOG.debug("Created JMXConnectorServer {}", connectorServer); 535 } 536 537 public String getConnectorPath() { 538 return connectorPath; 539 } 540 541 public void setConnectorPath(String connectorPath) { 542 this.connectorPath = connectorPath; 543 } 544 545 public int getConnectorPort() { 546 return connectorPort; 547 } 548 549 /** 550 * @org.apache.xbean.Property propertyEditor="org.apache.activemq.util.MemoryIntPropertyEditor" 551 */ 552 public void setConnectorPort(int connectorPort) { 553 this.connectorPort = connectorPort; 554 } 555 556 public int getRmiServerPort() { 557 return rmiServerPort; 558 } 559 560 /** 561 * @org.apache.xbean.Property propertyEditor="org.apache.activemq.util.MemoryIntPropertyEditor" 562 */ 563 public void setRmiServerPort(int rmiServerPort) { 564 this.rmiServerPort = rmiServerPort; 565 } 566 567 public boolean isCreateConnector() { 568 return createConnector; 569 } 570 571 /** 572 * @org.apache.xbean.Property propertyEditor="org.apache.activemq.util.BooleanEditor" 573 */ 574 public void setCreateConnector(boolean createConnector) { 575 this.createConnector = createConnector; 576 } 577 578 /** 579 * Get the connectorHost 580 * @return the connectorHost 581 */ 582 public String getConnectorHost() { 583 return this.connectorHost; 584 } 585 586 /** 587 * Set the connectorHost 588 * @param connectorHost the connectorHost to set 589 */ 590 public void setConnectorHost(String connectorHost) { 591 this.connectorHost = connectorHost; 592 } 593 594 public Map<String, ?> getEnvironment() { 595 return environment; 596 } 597 598 public void setEnvironment(Map<String, ?> environment) { 599 this.environment = environment; 600 } 601 602 public boolean isAllowRemoteAddressInMBeanNames() { 603 return allowRemoteAddressInMBeanNames; 604 } 605 606 public void setAllowRemoteAddressInMBeanNames(boolean allowRemoteAddressInMBeanNames) { 607 this.allowRemoteAddressInMBeanNames = allowRemoteAddressInMBeanNames; 608 } 609}