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