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 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    }