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