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 }