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