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 org.apache.activemq.Service;
020    import org.slf4j.Logger;
021    import org.slf4j.LoggerFactory;
022    
023    import javax.management.*;
024    import javax.management.remote.JMXConnectorServer;
025    import javax.management.remote.JMXConnectorServerFactory;
026    import javax.management.remote.JMXServiceURL;
027    import java.io.IOException;
028    import java.lang.reflect.Method;
029    import java.net.MalformedURLException;
030    import java.net.ServerSocket;
031    import java.rmi.registry.LocateRegistry;
032    import java.rmi.registry.Registry;
033    import java.rmi.server.RMIServerSocketFactory;
034    import java.util.*;
035    import java.util.concurrent.CopyOnWriteArrayList;
036    import java.util.concurrent.atomic.AtomicBoolean;
037    
038    /**
039     * An abstraction over JMX mbean registration
040     * 
041     * @org.apache.xbean.XBean
042     * 
043     */
044    public 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    }