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