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.camel.management;
018    
019    import java.io.IOException;
020    import java.lang.management.ManagementFactory;
021    import java.net.InetAddress;
022    import java.net.UnknownHostException;
023    import java.rmi.RemoteException;
024    import java.rmi.registry.LocateRegistry;
025    import java.util.HashSet;
026    import java.util.List;
027    import java.util.Set;
028    
029    import javax.management.JMException;
030    import javax.management.MBeanServer;
031    import javax.management.MBeanServerFactory;
032    import javax.management.NotCompliantMBeanException;
033    import javax.management.ObjectInstance;
034    import javax.management.ObjectName;
035    import javax.management.modelmbean.InvalidTargetObjectTypeException;
036    import javax.management.modelmbean.ModelMBeanInfo;
037    import javax.management.modelmbean.RequiredModelMBean;
038    import javax.management.remote.JMXConnectorServer;
039    import javax.management.remote.JMXConnectorServerFactory;
040    import javax.management.remote.JMXServiceURL;
041    
042    import org.apache.camel.impl.ServiceSupport;
043    import org.apache.camel.spi.InstrumentationAgent;
044    import org.apache.commons.logging.Log;
045    import org.apache.commons.logging.LogFactory;
046    import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource;
047    import org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler;
048    
049    /**
050     * Default implementation of the Camel JMX service agent
051     */
052    public class DefaultInstrumentationAgent extends ServiceSupport implements InstrumentationAgent {
053    
054        public static final String DEFAULT_DOMAIN = "org.apache.camel";
055        public static final String DEFAULT_HOST = "localhost";
056        public static final int DEFAULT_REGISTRY_PORT = 1099;
057        public static final int DEFAULT_CONNECTION_PORT = -1;
058        public static final String DEFAULT_SERVICE_URL_PATH = "/jmxrmi/camel";
059        private static final transient Log LOG = LogFactory.getLog(DefaultInstrumentationAgent.class);
060    
061        private MBeanServer server;
062        private Set<ObjectName> mbeans = new HashSet<ObjectName>();
063        private MetadataMBeanInfoAssembler assembler;
064        private JMXConnectorServer cs;
065    
066        private Integer registryPort;
067        private Integer connectorPort;
068        private String mBeanServerDefaultDomain;
069        private String mBeanObjectDomainName;
070        private String serviceUrlPath;
071        private Boolean usePlatformMBeanServer = true;
072        private Boolean createConnector;
073    
074        protected void finalizeSettings() {
075            if (registryPort == null) {
076                registryPort = Integer.getInteger(JmxSystemPropertyKeys.REGISTRY_PORT, DEFAULT_REGISTRY_PORT);
077            }
078    
079            if (connectorPort == null) {
080                connectorPort = Integer.getInteger(JmxSystemPropertyKeys.CONNECTOR_PORT, DEFAULT_CONNECTION_PORT);
081            }
082    
083            if (mBeanServerDefaultDomain == null) {
084                mBeanServerDefaultDomain = System.getProperty(JmxSystemPropertyKeys.DOMAIN, DEFAULT_DOMAIN);
085            }
086    
087            if (mBeanObjectDomainName == null) {
088                mBeanObjectDomainName = System.getProperty(JmxSystemPropertyKeys.MBEAN_DOMAIN, DEFAULT_DOMAIN);
089            }
090    
091            if (serviceUrlPath == null) {
092                serviceUrlPath = System.getProperty(JmxSystemPropertyKeys.SERVICE_URL_PATH, DEFAULT_SERVICE_URL_PATH);
093            }
094    
095            if (createConnector == null) {
096                createConnector = Boolean.getBoolean(JmxSystemPropertyKeys.CREATE_CONNECTOR);
097            }
098    
099            // "Use platform mbean server" is true by default
100            if (System.getProperty(JmxSystemPropertyKeys.USE_PLATFORM_MBS) != null) {
101                usePlatformMBeanServer = Boolean.getBoolean(JmxSystemPropertyKeys.USE_PLATFORM_MBS);
102            }
103        }
104    
105    
106        public void setRegistryPort(Integer value) {
107            registryPort = value;
108        }
109    
110        public void setConnectorPort(Integer value) {
111            connectorPort = value;
112        }
113    
114        public void setMBeanServerDefaultDomain(String value) {
115            mBeanServerDefaultDomain = value;
116        }
117    
118        public void setMBeanObjectDomainName(String value) {
119            mBeanObjectDomainName = value;
120        }
121    
122        public void setServiceUrlPath(String value) {
123            serviceUrlPath = value;
124        }
125    
126        public void setCreateConnector(Boolean flag) {
127            createConnector = flag;
128        }
129    
130        public void setUsePlatformMBeanServer(Boolean flag) {
131            usePlatformMBeanServer = flag;
132        }
133    
134        public MBeanServer getMBeanServer() {
135            return server;
136        }
137    
138        public void register(Object obj, ObjectName name) throws JMException {
139            register(obj, name, false);
140        }
141    
142        public void register(Object obj, ObjectName name, boolean forceRegistration) throws JMException {
143            try {
144                registerMBeanWithServer(obj, name, forceRegistration);
145            } catch (NotCompliantMBeanException e) {
146                // If this is not a "normal" MBean, then try to deploy it using JMX annotations
147                ModelMBeanInfo mbi;
148                mbi = assembler.getMBeanInfo(obj, name.toString());
149                RequiredModelMBean mbean = (RequiredModelMBean)server.instantiate(RequiredModelMBean.class.getName());
150                mbean.setModelMBeanInfo(mbi);
151                try {
152                    mbean.setManagedResource(obj, "ObjectReference");
153                } catch (InvalidTargetObjectTypeException itotex) {
154                    throw new JMException(itotex.getMessage());
155                }
156                registerMBeanWithServer(mbean, name, forceRegistration);
157            }
158        }
159    
160        public void unregister(ObjectName name) throws JMException {
161            server.unregisterMBean(name);
162        }
163    
164        protected void doStart() throws Exception {
165            assembler = new MetadataMBeanInfoAssembler();
166            assembler.setAttributeSource(new AnnotationJmxAttributeSource());
167    
168            // create mbean server if is has not be injected.
169            if (server == null) {
170                finalizeSettings();
171                createMBeanServer();
172            }
173    
174            if (LOG.isDebugEnabled()) {
175                LOG.debug("Starting JMX agent on server: " + getMBeanServer());
176            }
177        }
178    
179        protected void doStop() throws Exception {
180            // close JMX Connector
181            if (cs != null) {
182                try {
183                    cs.stop();
184                } catch (IOException e) {
185                    // ignore
186                }
187                cs = null;
188            }
189    
190            // Using the array to hold the busMBeans to avoid the
191            // CurrentModificationException
192            Object[] mBeans = mbeans.toArray();
193            int caught = 0;
194            for (Object name : mBeans) {
195                mbeans.remove(name);
196                try {
197                    unregister((ObjectName)name);
198                } catch (JMException jmex) {
199                    LOG.info("Exception unregistering MBean", jmex);
200                    caught++;
201                }
202            }
203            if (caught > 0) {
204                LOG.warn("A number of " + caught
205                         + " exceptions caught while unregistering MBeans during stop operation."
206                         + " See INFO log for details.");
207            }
208        }
209    
210        private void registerMBeanWithServer(Object obj, ObjectName name, boolean forceRegistration)
211            throws JMException {
212    
213            // have we already registered the bean, there can be shared instances in the camel routes
214            boolean exists = server.isRegistered(name);
215            if (exists) {
216                if (forceRegistration) {
217                    LOG.info("ForceRegistration enabled, unregistering existing MBean");
218                    server.unregisterMBean(name);
219                } else {
220                    // okay ignore we do not want to force it and it could be a shared instance
221                    if (LOG.isDebugEnabled()) {
222                        LOG.debug("MBean already registered with objectname: " + name);
223                    }
224                }
225            }
226    
227            // register bean if by force or not exsists
228            ObjectInstance instance = null;
229            if (forceRegistration || !exists) {
230                if (LOG.isTraceEnabled()) {
231                    LOG.trace("Registering MBean with objectname: " + name);
232                }
233                instance = server.registerMBean(obj, name);
234            }
235    
236            if (instance != null) {
237                ObjectName registeredName = instance.getObjectName();
238                if (LOG.isDebugEnabled()) {
239                    LOG.debug("Registered MBean with objectname: " + registeredName);
240                }
241                
242                mbeans.add(registeredName);
243            }
244        }
245    
246        protected void createMBeanServer() {
247            String hostName;
248            boolean canAccessSystemProps = true;
249            try {
250                // we'll do it this way mostly to determine if we should lookup the
251                // hostName
252                SecurityManager sm = System.getSecurityManager();
253                if (sm != null) {
254                    sm.checkPropertiesAccess();
255                }
256            } catch (SecurityException se) {
257                canAccessSystemProps = false;
258            }
259    
260            if (canAccessSystemProps) {
261                try {
262                    hostName = InetAddress.getLocalHost().getHostName();
263                } catch (UnknownHostException uhe) {
264                    LOG.info("Cannot determine localhost name. Using default: " + DEFAULT_REGISTRY_PORT, uhe);
265                    hostName = DEFAULT_HOST;
266                }
267            } else {
268                hostName = DEFAULT_HOST;
269            }
270    
271            server = findOrCreateMBeanServer();
272    
273            try {
274                // Create the connector if we need
275                if (createConnector) {
276                    createJmxConnector(hostName);
277                }
278            } catch (IOException ioe) {
279                LOG.warn("Could not create and start JMX connector.", ioe);
280            }
281        }
282    
283        @SuppressWarnings("unchecked")
284        protected MBeanServer findOrCreateMBeanServer() {
285    
286            // return platform mbean server if the option is specified.
287            if (usePlatformMBeanServer) {
288                return ManagementFactory.getPlatformMBeanServer();
289            }
290    
291            // look for the first mbean server that has match default domain name
292            List<MBeanServer> servers = (List<MBeanServer>)MBeanServerFactory.findMBeanServer(null);
293    
294            for (MBeanServer server : servers) {
295                if (LOG.isDebugEnabled()) {
296                    LOG.debug("Found MBeanServer with default domain " + server.getDefaultDomain());
297                }
298                
299                if (mBeanServerDefaultDomain.equals(server.getDefaultDomain())) {
300                    return server;
301                }
302            }
303    
304            // create a mbean server with the given default domain name
305            return MBeanServerFactory.createMBeanServer(mBeanServerDefaultDomain);
306        }
307    
308        protected void createJmxConnector(String host) throws IOException {
309            try {
310                LocateRegistry.createRegistry(registryPort);
311                if (LOG.isDebugEnabled()) {
312                    LOG.debug("Created JMXConnector RMI regisry on port " + registryPort);
313                }
314            } catch (RemoteException ex) {
315                // The registry may had been created, we could get the registry instead
316            }
317    
318            // Create an RMI connector and start it
319            JMXServiceURL url;
320    
321            if (connectorPort > 0) {
322                url = new JMXServiceURL("service:jmx:rmi://" + host + ":" + connectorPort + "/jndi/rmi://" + host
323                                        + ":" + registryPort + serviceUrlPath);
324            } else {
325                url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + host + ":" + registryPort
326                                        + serviceUrlPath);
327            }
328            cs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server);
329    
330            // Start the connector server asynchronously (in a separate thread).
331            Thread connectorThread = new Thread() {
332                public void run() {
333                    try {
334                        cs.start();
335                    } catch (IOException ioe) {
336                        LOG.warn("Could not start JMXConnector thread.", ioe);
337                    }
338                }
339            };
340            connectorThread.setName("Camel JMX Connector Thread [" + url + "]");
341            connectorThread.start();
342            LOG.info("JMX Connector thread started and listening at: " + url);
343        }
344    
345        public String getMBeanObjectDomainName() {
346            return mBeanObjectDomainName;
347        }
348    
349        public void setServer(MBeanServer value) {
350            server = value;
351        }
352    
353    }