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<Map.Entry<String,String>> 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 IOException { 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() { 172 if (suppressMBean != null) { 173 suppressMBeanList = new LinkedList<>(); 174 for (String pair : suppressMBean.split(",")) { 175 final String[] keyValue = pair.split("="); 176 suppressMBeanList.add(new Map.Entry<String, String>() { 177 @Override 178 public String getKey() { 179 return keyValue[0]; 180 } 181 182 @Override 183 public String getValue() { 184 return keyValue[1]; 185 } 186 187 @Override 188 public String setValue(String value) { 189 return null; 190 } 191 }); 192 } 193 } 194 } 195 196 @Override 197 public void stop() throws Exception { 198 if (started.compareAndSet(true, false)) { 199 MBeanServer mbeanServer = getMBeanServer(); 200 201 // unregister the mbeans we have registered 202 if (mbeanServer != null) { 203 for (Map.Entry<ObjectName, ObjectName> entry : registeredMBeanNames.entrySet()) { 204 ObjectName actualName = entry.getValue(); 205 if (actualName != null && beanServer.isRegistered(actualName)) { 206 LOG.debug("Unregistering MBean {}", actualName); 207 mbeanServer.unregisterMBean(actualName); 208 } 209 } 210 } 211 registeredMBeanNames.clear(); 212 213 JMXConnectorServer server = connectorServer; 214 connectorServer = null; 215 if (server != null) { 216 try { 217 if (!connectorStarting.get()) { 218 LOG.debug("Stopping jmx connector"); 219 server.stop(); 220 } 221 } catch (IOException e) { 222 LOG.warn("Failed to stop jmx connector: {}", e.getMessage()); 223 } 224 // stop naming service mbean 225 try { 226 if (namingServiceObjectName != null && getMBeanServer().isRegistered(namingServiceObjectName)) { 227 LOG.debug("Stopping MBean {}", namingServiceObjectName); 228 getMBeanServer().invoke(namingServiceObjectName, "stop", null, null); 229 LOG.debug("Unregistering MBean {}", namingServiceObjectName); 230 getMBeanServer().unregisterMBean(namingServiceObjectName); 231 } 232 } catch (Throwable ignore) { 233 LOG.warn("Error stopping and unregsitering MBean {} due to {}", namingServiceObjectName, ignore.getMessage()); 234 } 235 namingServiceObjectName = null; 236 } 237 238 if (locallyCreateMBeanServer && beanServer != null) { 239 // check to see if the factory knows about this server 240 List<MBeanServer> list = MBeanServerFactory.findMBeanServer(null); 241 if (list != null && !list.isEmpty() && list.contains(beanServer)) { 242 LOG.debug("Releasing MBeanServer {}", beanServer); 243 MBeanServerFactory.releaseMBeanServer(beanServer); 244 } 245 } 246 beanServer = null; 247 } 248 249 // Un-export JMX RMI registry, if it was created 250 if (registry != null) { 251 try { 252 UnicastRemoteObject.unexportObject(registry, true); 253 LOG.debug("Unexported JMX RMI Registry"); 254 } catch (NoSuchObjectException e) { 255 LOG.debug("Error occurred while unexporting JMX RMI registry. This exception will be ignored."); 256 } 257 258 registry = null; 259 } 260 } 261 262 /** 263 * Gets the broker name this context is used by, may be <tt>null</tt> 264 * if the broker name was not set. 265 */ 266 public String getBrokerName() { 267 return brokerName; 268 } 269 270 /** 271 * Sets the broker name this context is being used by. 272 */ 273 public void setBrokerName(String brokerName) { 274 this.brokerName = brokerName; 275 } 276 277 /** 278 * @return Returns the jmxDomainName. 279 */ 280 public String getJmxDomainName() { 281 return jmxDomainName; 282 } 283 284 /** 285 * @param jmxDomainName The jmxDomainName to set. 286 */ 287 public void setJmxDomainName(String jmxDomainName) { 288 this.jmxDomainName = jmxDomainName; 289 } 290 291 /** 292 * Get the MBeanServer 293 * 294 * @return the MBeanServer 295 */ 296 protected MBeanServer getMBeanServer() { 297 if (this.beanServer == null) { 298 this.beanServer = findMBeanServer(); 299 } 300 return beanServer; 301 } 302 303 /** 304 * Set the MBeanServer 305 * 306 * @param beanServer 307 */ 308 public void setMBeanServer(MBeanServer beanServer) { 309 this.beanServer = beanServer; 310 } 311 312 /** 313 * @return Returns the useMBeanServer. 314 */ 315 public boolean isUseMBeanServer() { 316 return useMBeanServer; 317 } 318 319 /** 320 * @param useMBeanServer The useMBeanServer to set. 321 */ 322 public void setUseMBeanServer(boolean useMBeanServer) { 323 this.useMBeanServer = useMBeanServer; 324 } 325 326 /** 327 * @return Returns the createMBeanServer flag. 328 */ 329 public boolean isCreateMBeanServer() { 330 return createMBeanServer; 331 } 332 333 /** 334 * @param enableJMX Set createMBeanServer. 335 */ 336 public void setCreateMBeanServer(boolean enableJMX) { 337 this.createMBeanServer = enableJMX; 338 } 339 340 public boolean isFindTigerMbeanServer() { 341 return findTigerMbeanServer; 342 } 343 344 public boolean isConnectorStarted() { 345 return connectorStarting.get() || (connectorServer != null && connectorServer.isActive()); 346 } 347 348 /** 349 * Enables/disables the searching for the Java 5 platform MBeanServer 350 */ 351 public void setFindTigerMbeanServer(boolean findTigerMbeanServer) { 352 this.findTigerMbeanServer = findTigerMbeanServer; 353 } 354 355 /** 356 * Formulate and return the MBean ObjectName of a custom control MBean 357 * 358 * @param type 359 * @param name 360 * @return the JMX ObjectName of the MBean, or <code>null</code> if 361 * <code>customName</code> is invalid. 362 */ 363 public ObjectName createCustomComponentMBeanName(String type, String name) { 364 ObjectName result = null; 365 String tmp = jmxDomainName + ":" + "type=" + sanitizeString(type) + ",name=" + sanitizeString(name); 366 try { 367 result = new ObjectName(tmp); 368 } catch (MalformedObjectNameException e) { 369 LOG.error("Couldn't create ObjectName from: {}, {}", type, name); 370 } 371 return result; 372 } 373 374 /** 375 * The ':' and '/' characters are reserved in ObjectNames 376 * 377 * @param in 378 * @return sanitized String 379 */ 380 private static String sanitizeString(String in) { 381 String result = null; 382 if (in != null) { 383 result = in.replace(':', '_'); 384 result = result.replace('/', '_'); 385 result = result.replace('\\', '_'); 386 } 387 return result; 388 } 389 390 /** 391 * Retrieve an System ObjectName 392 * 393 * @param domainName 394 * @param containerName 395 * @param theClass 396 * @return the ObjectName 397 * @throws MalformedObjectNameException 398 */ 399 public static ObjectName getSystemObjectName(String domainName, String containerName, Class<?> theClass) throws MalformedObjectNameException, NullPointerException { 400 String tmp = domainName + ":" + "type=" + theClass.getName() + ",name=" + getRelativeName(containerName, theClass); 401 return new ObjectName(tmp); 402 } 403 404 private static String getRelativeName(String containerName, Class<?> theClass) { 405 String name = theClass.getName(); 406 int index = name.lastIndexOf("."); 407 if (index >= 0 && (index + 1) < name.length()) { 408 name = name.substring(index + 1); 409 } 410 return containerName + "." + name; 411 } 412 413 public Object newProxyInstance(ObjectName objectName, Class<?> interfaceClass, boolean notificationBroadcaster){ 414 return MBeanServerInvocationHandler.newProxyInstance(getMBeanServer(), objectName, interfaceClass, notificationBroadcaster); 415 } 416 417 public Object getAttribute(ObjectName name, String attribute) throws Exception{ 418 return getMBeanServer().getAttribute(name, attribute); 419 } 420 421 public ObjectInstance registerMBean(Object bean, ObjectName name) throws Exception{ 422 ObjectInstance result = null; 423 if (isAllowedToRegister(name)) { 424 result = getMBeanServer().registerMBean(bean, name); 425 this.registeredMBeanNames.put(name, result.getObjectName()); 426 } 427 return result; 428 } 429 430 private boolean isAllowedToRegister(ObjectName name) { 431 boolean result = true; 432 if (suppressMBean != null && suppressMBeanList != null) { 433 for (Map.Entry<String,String> attr : suppressMBeanList) { 434 if (attr.getValue().equals(name.getKeyProperty(attr.getKey()))) { 435 result = false; 436 break; 437 } 438 } 439 } 440 return result; 441 } 442 443 public Set<ObjectName> queryNames(ObjectName name, QueryExp query) throws Exception{ 444 if (name != null) { 445 ObjectName actualName = this.registeredMBeanNames.get(name); 446 if (actualName != null) { 447 return getMBeanServer().queryNames(actualName, query); 448 } 449 } 450 return getMBeanServer().queryNames(name, query); 451 } 452 453 public ObjectInstance getObjectInstance(ObjectName name) throws InstanceNotFoundException { 454 return getMBeanServer().getObjectInstance(name); 455 } 456 457 /** 458 * Unregister an MBean 459 * 460 * @param name 461 * @throws JMException 462 */ 463 public void unregisterMBean(ObjectName name) throws JMException { 464 ObjectName actualName = this.registeredMBeanNames.get(name); 465 if (beanServer != null && actualName != null && beanServer.isRegistered(actualName) && this.registeredMBeanNames.remove(name) != null) { 466 LOG.debug("Unregistering MBean {}", actualName); 467 beanServer.unregisterMBean(actualName); 468 } 469 } 470 471 protected synchronized MBeanServer findMBeanServer() { 472 MBeanServer result = null; 473 474 try { 475 if (useMBeanServer) { 476 if (findTigerMbeanServer) { 477 result = findTigerMBeanServer(); 478 } 479 if (result == null) { 480 // lets piggy back on another MBeanServer - we could be in an appserver! 481 List<MBeanServer> list = MBeanServerFactory.findMBeanServer(null); 482 if (list != null && list.size() > 0) { 483 result = list.get(0); 484 } 485 } 486 } 487 if (result == null && createMBeanServer) { 488 result = createMBeanServer(); 489 } 490 } catch (NoClassDefFoundError e) { 491 LOG.error("Could not load MBeanServer", e); 492 } catch (Throwable e) { 493 // probably don't have access to system properties 494 LOG.error("Failed to initialize MBeanServer", e); 495 } 496 return result; 497 } 498 499 public MBeanServer findTigerMBeanServer() { 500 String name = "java.lang.management.ManagementFactory"; 501 Class<?> type = loadClass(name, ManagementContext.class.getClassLoader()); 502 if (type != null) { 503 try { 504 Method method = type.getMethod("getPlatformMBeanServer", new Class[0]); 505 if (method != null) { 506 Object answer = method.invoke(null, new Object[0]); 507 if (answer instanceof MBeanServer) { 508 if (createConnector) { 509 createConnector((MBeanServer)answer); 510 } 511 return (MBeanServer)answer; 512 } else { 513 LOG.warn("Could not cast: {} into an MBeanServer. There must be some classloader strangeness in town", answer); 514 } 515 } else { 516 LOG.warn("Method getPlatformMBeanServer() does not appear visible on type: {}", type.getName()); 517 } 518 } catch (Exception e) { 519 LOG.warn("Failed to call getPlatformMBeanServer() due to: ", e); 520 } 521 } else { 522 LOG.trace("Class not found: {} so probably running on Java 1.4", name); 523 } 524 return null; 525 } 526 527 private static Class<?> loadClass(String name, ClassLoader loader) { 528 try { 529 return loader.loadClass(name); 530 } catch (ClassNotFoundException e) { 531 try { 532 return Thread.currentThread().getContextClassLoader().loadClass(name); 533 } catch (ClassNotFoundException e1) { 534 return null; 535 } 536 } 537 } 538 539 /** 540 * @return 541 * @throws NullPointerException 542 * @throws MalformedObjectNameException 543 * @throws IOException 544 */ 545 protected MBeanServer createMBeanServer() throws MalformedObjectNameException, IOException { 546 MBeanServer mbeanServer = MBeanServerFactory.createMBeanServer(jmxDomainName); 547 locallyCreateMBeanServer = true; 548 if (createConnector) { 549 createConnector(mbeanServer); 550 } 551 return mbeanServer; 552 } 553 554 /** 555 * @param mbeanServer 556 * @throws MalformedObjectNameException 557 * @throws IOException 558 */ 559 private void createConnector(MBeanServer mbeanServer) throws MalformedObjectNameException, IOException { 560 // Create the NamingService, needed by JSR 160 561 try { 562 if (registry == null) { 563 LOG.debug("Creating RMIRegistry on port {}", connectorPort); 564 registry = LocateRegistry.createRegistry(connectorPort); 565 } 566 namingServiceObjectName = ObjectName.getInstance("naming:type=rmiregistry"); 567 568 // Do not use the createMBean as the mx4j jar may not be in the 569 // same class loader than the server 570 Class<?> cl = Class.forName("mx4j.tools.naming.NamingService"); 571 mbeanServer.registerMBean(cl.newInstance(), namingServiceObjectName); 572 573 // set the naming port 574 Attribute attr = new Attribute("Port", Integer.valueOf(connectorPort)); 575 mbeanServer.setAttribute(namingServiceObjectName, attr); 576 } catch(ClassNotFoundException e) { 577 LOG.debug("Probably not using JRE 1.4: {}", e.getLocalizedMessage()); 578 } catch (Throwable e) { 579 LOG.debug("Failed to create local registry. This exception will be ignored.", e); 580 } 581 582 // Create the JMXConnectorServer 583 String rmiServer = ""; 584 if (rmiServerPort != 0) { 585 // This is handy to use if you have a firewall and need to force JMX to use fixed ports. 586 rmiServer = ""+getConnectorHost()+":" + rmiServerPort; 587 } 588 String serviceURL = "service:jmx:rmi://" + rmiServer + "/jndi/rmi://" +getConnectorHost()+":" + connectorPort + connectorPath; 589 JMXServiceURL url = new JMXServiceURL(serviceURL); 590 connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, environment, mbeanServer); 591 592 LOG.debug("Created JMXConnectorServer {}", connectorServer); 593 } 594 595 public String getConnectorPath() { 596 return connectorPath; 597 } 598 599 public void setConnectorPath(String connectorPath) { 600 this.connectorPath = connectorPath; 601 } 602 603 public int getConnectorPort() { 604 return connectorPort; 605 } 606 607 /** 608 * @org.apache.xbean.Property propertyEditor="org.apache.activemq.util.MemoryIntPropertyEditor" 609 */ 610 public void setConnectorPort(int connectorPort) { 611 this.connectorPort = connectorPort; 612 } 613 614 public int getRmiServerPort() { 615 return rmiServerPort; 616 } 617 618 /** 619 * @org.apache.xbean.Property propertyEditor="org.apache.activemq.util.MemoryIntPropertyEditor" 620 */ 621 public void setRmiServerPort(int rmiServerPort) { 622 this.rmiServerPort = rmiServerPort; 623 } 624 625 public boolean isCreateConnector() { 626 return createConnector; 627 } 628 629 /** 630 * @org.apache.xbean.Property propertyEditor="org.apache.activemq.util.BooleanEditor" 631 */ 632 public void setCreateConnector(boolean createConnector) { 633 this.createConnector = createConnector; 634 } 635 636 /** 637 * Get the connectorHost 638 * @return the connectorHost 639 */ 640 public String getConnectorHost() { 641 return this.connectorHost; 642 } 643 644 /** 645 * Set the connectorHost 646 * @param connectorHost the connectorHost to set 647 */ 648 public void setConnectorHost(String connectorHost) { 649 this.connectorHost = connectorHost; 650 } 651 652 public Map<String, ?> getEnvironment() { 653 return environment; 654 } 655 656 public void setEnvironment(Map<String, ?> environment) { 657 this.environment = environment; 658 } 659 660 public boolean isAllowRemoteAddressInMBeanNames() { 661 return allowRemoteAddressInMBeanNames; 662 } 663 664 public void setAllowRemoteAddressInMBeanNames(boolean allowRemoteAddressInMBeanNames) { 665 this.allowRemoteAddressInMBeanNames = allowRemoteAddressInMBeanNames; 666 } 667 668 /** 669 * Allow selective MBeans registration to be suppressed. Any Mbean ObjectName that matches any 670 * of the supplied attribute values will not be registered with the MBeanServer. 671 * eg: "endpoint=dynamicProducer,endpoint=Consumer" will suppress the registration of *all* dynamic producer and consumer mbeans. 672 * 673 * @param commaListOfAttributeKeyValuePairs the comma separated list of attribute key=value pairs to match. 674 */ 675 public void setSuppressMBean(String commaListOfAttributeKeyValuePairs) { 676 this.suppressMBean = commaListOfAttributeKeyValuePairs; 677 } 678 679 public String getSuppressMBean() { 680 return suppressMBean; 681 } 682}