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