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 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 }