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 org.apache.activemq.Service; 020import org.slf4j.Logger; 021import org.slf4j.LoggerFactory; 022 023import javax.management.*; 024import javax.management.remote.JMXConnectorServer; 025import javax.management.remote.JMXServiceURL; 026import java.io.IOException; 027import java.lang.management.ManagementFactory; 028import java.lang.reflect.Method; 029import java.rmi.AccessException; 030import java.rmi.AlreadyBoundException; 031import java.net.MalformedURLException; 032import java.net.ServerSocket; 033import java.rmi.NotBoundException; 034import java.rmi.Remote; 035import java.rmi.RemoteException; 036import java.rmi.registry.Registry; 037import java.rmi.server.RMIServerSocketFactory; 038import java.util.*; 039import java.util.concurrent.CopyOnWriteArrayList; 040import java.util.concurrent.atomic.AtomicBoolean; 041import javax.management.remote.rmi.RMIConnectorServer; 042import javax.management.remote.rmi.RMIJRMPServerImpl; 043 044 045/** 046 * An abstraction over JMX mbean registration 047 * 048 * @org.apache.xbean.XBean 049 * 050 */ 051public class ManagementContext implements Service { 052 /** 053 * Default activemq domain 054 */ 055 public static final String DEFAULT_DOMAIN = "org.apache.activemq"; 056 private static final Logger LOG = LoggerFactory.getLogger(ManagementContext.class); 057 private MBeanServer beanServer; 058 private String jmxDomainName = DEFAULT_DOMAIN; 059 private boolean useMBeanServer = true; 060 private boolean createMBeanServer = true; 061 private boolean locallyCreateMBeanServer; 062 private boolean createConnector = true; 063 private boolean findTigerMbeanServer = true; 064 private String connectorHost = "localhost"; 065 private int connectorPort = 1099; 066 private Map environment; 067 private int rmiServerPort; 068 private String connectorPath = "/jmxrmi"; 069 private final AtomicBoolean started = new AtomicBoolean(false); 070 private final AtomicBoolean connectorStarting = new AtomicBoolean(false); 071 private JMXConnectorServer connectorServer; 072 private ObjectName namingServiceObjectName; 073 private Registry registry; 074 private ServerSocket registrySocket; 075 private final List<ObjectName> registeredMBeanNames = new CopyOnWriteArrayList<ObjectName>(); 076 private boolean allowRemoteAddressInMBeanNames = true; 077 private Remote serverStub; 078 private RMIJRMPServerImpl server; 079 080 public ManagementContext() { 081 this(null); 082 } 083 084 public ManagementContext(MBeanServer server) { 085 this.beanServer = server; 086 } 087 088 public void start() throws IOException { 089 // lets force the MBeanServer to be created if needed 090 if (started.compareAndSet(false, true)) { 091 getMBeanServer(); 092 if (connectorServer != null) { 093 try { 094 getMBeanServer().invoke(namingServiceObjectName, "start", null, null); 095 } catch (Throwable ignore) { 096 } 097 Thread t = new Thread("JMX connector") { 098 @Override 099 public void run() { 100 try { 101 if (started.get() && server != null) { 102 LOG.debug("Starting JMXConnectorServer..."); 103 connectorStarting.set(true); 104 try { 105 connectorServer.start(); 106 serverStub = server.toStub(); 107 } finally { 108 connectorStarting.set(false); 109 } 110 LOG.info("JMX consoles can connect to " + connectorServer.getAddress()); 111 } 112 } catch (IOException e) { 113 LOG.warn("Failed to start jmx connector: " + e.getMessage()); 114 LOG.debug("Reason for failed jms connector start", e); 115 } 116 } 117 }; 118 t.setDaemon(true); 119 t.start(); 120 } 121 } 122 } 123 124 public void stop() throws Exception { 125 if (started.compareAndSet(true, false)) { 126 MBeanServer mbeanServer = getMBeanServer(); 127 if (mbeanServer != null) { 128 for (Iterator<ObjectName> iter = registeredMBeanNames.iterator(); iter.hasNext();) { 129 ObjectName name = iter.next(); 130 131 mbeanServer.unregisterMBean(name); 132 133 } 134 } 135 registeredMBeanNames.clear(); 136 JMXConnectorServer server = connectorServer; 137 connectorServer = null; 138 if (server != null) { 139 try { 140 if (!connectorStarting.get()) { 141 server.stop(); 142 } 143 } catch (IOException e) { 144 LOG.warn("Failed to stop jmx connector: " + e.getMessage()); 145 } 146 try { 147 getMBeanServer().invoke(namingServiceObjectName, "stop", null, null); 148 } catch (Throwable ignore) { 149 } 150 } 151 if (locallyCreateMBeanServer && beanServer != null) { 152 // check to see if the factory knows about this server 153 List list = MBeanServerFactory.findMBeanServer(null); 154 if (list != null && !list.isEmpty() && list.contains(beanServer)) { 155 MBeanServerFactory.releaseMBeanServer(beanServer); 156 } 157 } 158 beanServer = null; 159 if(registrySocket!=null) { 160 try { 161 registrySocket.close(); 162 } catch (IOException e) { 163 } 164 registrySocket = null; 165 } 166 } 167 } 168 169 /** 170 * @return Returns the jmxDomainName. 171 */ 172 public String getJmxDomainName() { 173 return jmxDomainName; 174 } 175 176 /** 177 * @param jmxDomainName The jmxDomainName to set. 178 */ 179 public void setJmxDomainName(String jmxDomainName) { 180 this.jmxDomainName = jmxDomainName; 181 } 182 183 /** 184 * Get the MBeanServer 185 * 186 * @return the MBeanServer 187 */ 188 protected MBeanServer getMBeanServer() { 189 if (this.beanServer == null) { 190 this.beanServer = findMBeanServer(); 191 } 192 return beanServer; 193 } 194 195 /** 196 * Set the MBeanServer 197 * 198 * @param beanServer 199 */ 200 public void setMBeanServer(MBeanServer beanServer) { 201 this.beanServer = beanServer; 202 } 203 204 /** 205 * @return Returns the useMBeanServer. 206 */ 207 public boolean isUseMBeanServer() { 208 return useMBeanServer; 209 } 210 211 /** 212 * @param useMBeanServer The useMBeanServer to set. 213 */ 214 public void setUseMBeanServer(boolean useMBeanServer) { 215 this.useMBeanServer = useMBeanServer; 216 } 217 218 /** 219 * @return Returns the createMBeanServer flag. 220 */ 221 public boolean isCreateMBeanServer() { 222 return createMBeanServer; 223 } 224 225 /** 226 * @param enableJMX Set createMBeanServer. 227 */ 228 public void setCreateMBeanServer(boolean enableJMX) { 229 this.createMBeanServer = enableJMX; 230 } 231 232 public boolean isFindTigerMbeanServer() { 233 return findTigerMbeanServer; 234 } 235 236 public boolean isConnectorStarted() { 237 return connectorStarting.get() || (connectorServer != null && connectorServer.isActive()); 238 } 239 240 /** 241 * Enables/disables the searching for the Java 5 platform MBeanServer 242 */ 243 public void setFindTigerMbeanServer(boolean findTigerMbeanServer) { 244 this.findTigerMbeanServer = findTigerMbeanServer; 245 } 246 247 /** 248 * Formulate and return the MBean ObjectName of a custom control MBean 249 * 250 * @param type 251 * @param name 252 * @return the JMX ObjectName of the MBean, or <code>null</code> if 253 * <code>customName</code> is invalid. 254 */ 255 public ObjectName createCustomComponentMBeanName(String type, String name) { 256 ObjectName result = null; 257 String tmp = jmxDomainName + ":" + "type=" + sanitizeString(type) + ",name=" + sanitizeString(name); 258 try { 259 result = new ObjectName(tmp); 260 } catch (MalformedObjectNameException e) { 261 LOG.error("Couldn't create ObjectName from: " + type + " , " + name); 262 } 263 return result; 264 } 265 266 /** 267 * The ':' and '/' characters are reserved in ObjectNames 268 * 269 * @param in 270 * @return sanitized String 271 */ 272 private static String sanitizeString(String in) { 273 String result = null; 274 if (in != null) { 275 result = in.replace(':', '_'); 276 result = result.replace('/', '_'); 277 result = result.replace('\\', '_'); 278 } 279 return result; 280 } 281 282 /** 283 * Retrive an System ObjectName 284 * 285 * @param domainName 286 * @param containerName 287 * @param theClass 288 * @return the ObjectName 289 * @throws MalformedObjectNameException 290 */ 291 public static ObjectName getSystemObjectName(String domainName, String containerName, Class theClass) throws MalformedObjectNameException, NullPointerException { 292 String tmp = domainName + ":" + "type=" + theClass.getName() + ",name=" + getRelativeName(containerName, theClass); 293 return new ObjectName(tmp); 294 } 295 296 private static String getRelativeName(String containerName, Class theClass) { 297 String name = theClass.getName(); 298 int index = name.lastIndexOf("."); 299 if (index >= 0 && (index + 1) < name.length()) { 300 name = name.substring(index + 1); 301 } 302 return containerName + "." + name; 303 } 304 305 public Object newProxyInstance( ObjectName objectName, 306 Class interfaceClass, 307 boolean notificationBroadcaster){ 308 return MBeanServerInvocationHandler.newProxyInstance(getMBeanServer(), objectName, interfaceClass, notificationBroadcaster); 309 310 } 311 312 public Object getAttribute(ObjectName name, String attribute) throws Exception{ 313 return getMBeanServer().getAttribute(name, attribute); 314 } 315 316 public ObjectInstance registerMBean(Object bean, ObjectName name) throws Exception{ 317 ObjectInstance result = getMBeanServer().registerMBean(bean, name); 318 this.registeredMBeanNames.add(name); 319 return result; 320 } 321 322 public Set<ObjectName> queryNames(ObjectName name, QueryExp query) throws Exception{ 323 return getMBeanServer().queryNames(name, query); 324 } 325 326 public ObjectInstance getObjectInstance(ObjectName name) throws InstanceNotFoundException { 327 return getMBeanServer().getObjectInstance(name); 328 } 329 330 /** 331 * Unregister an MBean 332 * 333 * @param name 334 * @throws JMException 335 */ 336 public void unregisterMBean(ObjectName name) throws JMException { 337 if (beanServer != null && beanServer.isRegistered(name) && this.registeredMBeanNames.remove(name)) { 338 beanServer.unregisterMBean(name); 339 } 340 } 341 342 protected synchronized MBeanServer findMBeanServer() { 343 MBeanServer result = null; 344 // create the mbean server 345 try { 346 if (useMBeanServer) { 347 if (findTigerMbeanServer) { 348 result = findTigerMBeanServer(); 349 } 350 if (result == null) { 351 // lets piggy back on another MBeanServer - 352 // we could be in an appserver! 353 List list = MBeanServerFactory.findMBeanServer(null); 354 if (list != null && list.size() > 0) { 355 result = (MBeanServer)list.get(0); 356 } 357 } 358 } 359 if (result == null && createMBeanServer) { 360 result = createMBeanServer(); 361 } 362 } catch (NoClassDefFoundError e) { 363 LOG.error("Could not load MBeanServer", e); 364 } catch (Throwable e) { 365 // probably don't have access to system properties 366 LOG.error("Failed to initialize MBeanServer", e); 367 } 368 return result; 369 } 370 371 public MBeanServer findTigerMBeanServer() { 372 String name = "java.lang.management.ManagementFactory"; 373 Class type = loadClass(name, ManagementContext.class.getClassLoader()); 374 if (type != null) { 375 try { 376 Method method = type.getMethod("getPlatformMBeanServer", new Class[0]); 377 if (method != null) { 378 Object answer = method.invoke(null, new Object[0]); 379 if (answer instanceof MBeanServer) { 380 if (createConnector) { 381 createConnector((MBeanServer)answer); 382 } 383 return (MBeanServer)answer; 384 } else { 385 LOG.warn("Could not cast: " + answer + " into an MBeanServer. There must be some classloader strangeness in town"); 386 } 387 } else { 388 LOG.warn("Method getPlatformMBeanServer() does not appear visible on type: " + type.getName()); 389 } 390 } catch (Exception e) { 391 LOG.warn("Failed to call getPlatformMBeanServer() due to: " + e, e); 392 } 393 } else { 394 LOG.trace("Class not found: " + name + " so probably running on Java 1.4"); 395 } 396 return null; 397 } 398 399 private static Class loadClass(String name, ClassLoader loader) { 400 try { 401 return loader.loadClass(name); 402 } catch (ClassNotFoundException e) { 403 try { 404 return Thread.currentThread().getContextClassLoader().loadClass(name); 405 } catch (ClassNotFoundException e1) { 406 return null; 407 } 408 } 409 } 410 411 /** 412 * @return 413 * @throws NullPointerException 414 * @throws MalformedObjectNameException 415 * @throws IOException 416 */ 417 protected MBeanServer createMBeanServer() throws MalformedObjectNameException, IOException { 418 MBeanServer mbeanServer = MBeanServerFactory.createMBeanServer(jmxDomainName); 419 locallyCreateMBeanServer = true; 420 if (createConnector) { 421 createConnector(mbeanServer); 422 } 423 return mbeanServer; 424 } 425 426 /** 427 * @param mbeanServer 428 * @throws MalformedObjectNameException 429 * @throws MalformedURLException 430 * @throws IOException 431 */ 432 private void createConnector(MBeanServer mbeanServer) throws MalformedObjectNameException, MalformedURLException, IOException { 433 // Create the NamingService, needed by JSR 160 434 try { 435 if (registry == null) { 436 registry = new JmxRegistry(connectorPort); 437 } 438 namingServiceObjectName = ObjectName.getInstance("naming:type=rmiregistry"); 439 440 // Do not use the createMBean as the mx4j jar may not be in the 441 // same class loader than the server 442 Class cl = Class.forName("mx4j.tools.naming.NamingService"); 443 mbeanServer.registerMBean(cl.newInstance(), namingServiceObjectName); 444 // mbeanServer.createMBean("mx4j.tools.naming.NamingService", 445 // namingServiceObjectName, null); 446 // set the naming port 447 Attribute attr = new Attribute("Port", Integer.valueOf(connectorPort)); 448 mbeanServer.setAttribute(namingServiceObjectName, attr); 449 } catch(ClassNotFoundException e) { 450 LOG.debug("Probably not using JRE 1.4: " + e.getLocalizedMessage()); 451 } 452 catch (Throwable e) { 453 LOG.debug("Failed to create local registry", e); 454 } 455 // Create the JMXConnectorServer 456 String rmiServer = ""; 457 if (rmiServerPort != 0) { 458 // This is handy to use if you have a firewall and need to 459 // force JMX to use fixed ports. 460 rmiServer = ""+getConnectorHost()+":" + rmiServerPort; 461 } 462 463 server = new RMIJRMPServerImpl(connectorPort, null, null, environment); 464 465 final String serviceURL = "service:jmx:rmi://" + rmiServer + "/jndi/rmi://" +getConnectorHost()+":" + connectorPort + connectorPath; 466 final JMXServiceURL url = new JMXServiceURL(serviceURL); 467 468 connectorServer = new RMIConnectorServer(url, environment, server, ManagementFactory.getPlatformMBeanServer()); 469 } 470 471 public String getConnectorPath() { 472 return connectorPath; 473 } 474 475 public void setConnectorPath(String connectorPath) { 476 this.connectorPath = connectorPath; 477 } 478 479 public int getConnectorPort() { 480 return connectorPort; 481 } 482 483 /** 484 * @org.apache.xbean.Property propertyEditor="org.apache.activemq.util.MemoryIntPropertyEditor" 485 */ 486 public void setConnectorPort(int connectorPort) { 487 this.connectorPort = connectorPort; 488 } 489 490 public int getRmiServerPort() { 491 return rmiServerPort; 492 } 493 494 /** 495 * @org.apache.xbean.Property propertyEditor="org.apache.activemq.util.MemoryIntPropertyEditor" 496 */ 497 public void setRmiServerPort(int rmiServerPort) { 498 this.rmiServerPort = rmiServerPort; 499 } 500 501 public boolean isCreateConnector() { 502 return createConnector; 503 } 504 505 /** 506 * @org.apache.xbean.Property propertyEditor="org.apache.activemq.util.BooleanEditor" 507 */ 508 public void setCreateConnector(boolean createConnector) { 509 this.createConnector = createConnector; 510 } 511 512 /** 513 * Get the connectorHost 514 * @return the connectorHost 515 */ 516 public String getConnectorHost() { 517 return this.connectorHost; 518 } 519 520 /** 521 * Set the connectorHost 522 * @param connectorHost the connectorHost to set 523 */ 524 public void setConnectorHost(String connectorHost) { 525 this.connectorHost = connectorHost; 526 } 527 528 public Map getEnvironment() { 529 return environment; 530 } 531 532 public void setEnvironment(Map environment) { 533 this.environment = environment; 534 } 535 536 public boolean isAllowRemoteAddressInMBeanNames() { 537 return allowRemoteAddressInMBeanNames; 538 } 539 540 public void setAllowRemoteAddressInMBeanNames(boolean allowRemoteAddressInMBeanNames) { 541 this.allowRemoteAddressInMBeanNames = allowRemoteAddressInMBeanNames; 542 } 543 544 /* 545 * Better to use the internal API than re-invent the wheel. 546 */ 547 @SuppressWarnings("restriction") 548 private class JmxRegistry extends sun.rmi.registry.RegistryImpl { 549 public static final String LOOKUP_NAME = "jmxrmi"; 550 551 public JmxRegistry(int port) throws RemoteException { 552 super(port); 553 } 554 555 @Override 556 557 public Remote lookup(String s) throws RemoteException, NotBoundException { 558 return LOOKUP_NAME.equals(s) ? serverStub : null; 559 } 560 561 @Override 562 public void bind(String s, Remote remote) throws RemoteException, AlreadyBoundException, AccessException { 563 } 564 565 @Override 566 public void unbind(String s) throws RemoteException, NotBoundException, AccessException { 567 } 568 569 @Override 570 public void rebind(String s, Remote remote) throws RemoteException, AccessException { 571 } 572 573 @Override 574 public String[] list() throws RemoteException { 575 return new String[] {LOOKUP_NAME}; 576 } 577 } 578}