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 */
017package org.apache.activemq.broker.jmx;
018
019import java.io.IOException;
020import java.lang.reflect.Method;
021import java.rmi.NoSuchObjectException;
022import java.rmi.registry.LocateRegistry;
023import java.rmi.registry.Registry;
024import java.rmi.server.UnicastRemoteObject;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028import java.util.concurrent.ConcurrentHashMap;
029import java.util.concurrent.atomic.AtomicBoolean;
030
031import javax.management.Attribute;
032import javax.management.InstanceNotFoundException;
033import javax.management.JMException;
034import javax.management.MBeanServer;
035import javax.management.MBeanServerFactory;
036import javax.management.MBeanServerInvocationHandler;
037import javax.management.MalformedObjectNameException;
038import javax.management.ObjectInstance;
039import javax.management.ObjectName;
040import javax.management.QueryExp;
041import javax.management.remote.JMXConnectorServer;
042import javax.management.remote.JMXConnectorServerFactory;
043import javax.management.remote.JMXServiceURL;
044
045import org.apache.activemq.Service;
046import org.slf4j.Logger;
047import org.slf4j.LoggerFactory;
048import org.slf4j.MDC;
049
050/**
051 * An abstraction over JMX mbean registration
052 *
053 * @org.apache.xbean.XBean
054 *
055 */
056public 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 {}. This exception is ignored.", namingServiceObjectName, 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 {}. Will restart management to re-create JMX connector, trying to remedy this issue.", e.getMessage());
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 {} due to {}", namingServiceObjectName, 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: {} into an MBeanServer. There must be some classloader strangeness in town", answer);
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);
462            }
463        } else {
464            LOG.trace("Class not found: {} so probably running on Java 1.4", name);
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}