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