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