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