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