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