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    }