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