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