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