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    }