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            ObjectInstance instance = null;
223            try {
224                instance = server.registerMBean(obj, name);
225            } catch (InstanceAlreadyExistsException e) {
226                if (forceRegistration) {
227                    server.unregisterMBean(name);
228                    instance = server.registerMBean(obj, name);
229                } else {
230                    throw e;
231                }
232            }
233    
234            if (instance != null) {
235                if (LOG.isDebugEnabled()) {
236                    LOG.debug("Registered objectname " + instance.getObjectName());
237                }
238                
239                mbeans.add(instance.getObjectName());
240            }
241        }
242    
243        protected void createMBeanServer() {
244            String hostName = DEFAULT_HOST;
245            boolean canAccessSystemProps = true;
246            try {
247                // we'll do it this way mostly to determine if we should lookup the
248                // hostName
249                SecurityManager sm = System.getSecurityManager();
250                if (sm != null) {
251                    sm.checkPropertiesAccess();
252                }
253            } catch (SecurityException se) {
254                canAccessSystemProps = false;
255            }
256    
257            if (canAccessSystemProps) {
258                try {
259                    hostName = InetAddress.getLocalHost().getHostName();
260                } catch (UnknownHostException uhe) {
261                    LOG.info("Cannot determine localhost name. Using default: "
262                             + DEFAULT_REGISTRY_PORT, uhe);
263                    hostName = DEFAULT_HOST;
264                }
265            } else {
266                hostName = DEFAULT_HOST;
267            }
268    
269            server = findOrCreateMBeanServer();
270    
271            try {
272                // Create the connector if we need
273                if (createConnector) {
274                    createJmxConnector(hostName);
275                }
276            } catch (IOException ioe) {
277                LOG.warn("Could not create and start jmx connector.", ioe);
278            }
279        }
280    
281        @SuppressWarnings("unchecked")
282        protected MBeanServer findOrCreateMBeanServer() {
283    
284            // return platform mbean server if the option is specified.
285            if (Boolean.getBoolean(JmxSystemPropertyKeys.USE_PLATFORM_MBS) || usePlatformMBeanServer) {
286                return ManagementFactory.getPlatformMBeanServer();
287            }
288    
289            // look for the first mbean server that has match default domain name
290            List<MBeanServer> servers =
291                (List<MBeanServer>)MBeanServerFactory.findMBeanServer(null);
292    
293            for (MBeanServer server : servers) {
294                if (LOG.isDebugEnabled()) {
295                    LOG.debug("Found MBeanServer with default domain " + server.getDefaultDomain());
296                }
297                
298                if (mBeanServerDefaultDomain.equals(server.getDefaultDomain())) {
299                    return server;
300                }
301            }
302    
303            // create a mbean server with the given default domain name
304            return MBeanServerFactory.createMBeanServer(mBeanServerDefaultDomain);
305        }
306    
307        protected void createJmxConnector(String host) throws IOException {
308            try {
309                LocateRegistry.createRegistry(registryPort);
310                if (LOG.isDebugEnabled()) {
311                    LOG.debug("Created RMI regisry on port " + registryPort);
312                }
313            } catch (RemoteException ex) {
314                // The registry may had been created, we could get the registry instead
315            }
316    
317            // Create an RMI connector and start it
318            JMXServiceURL url;
319    
320            if (connectorPort > 0) {
321                url = new JMXServiceURL("service:jmx:rmi://" + host + ":" + connectorPort + "/jndi/rmi://" + host
322                                        + ":" + registryPort + serviceUrlPath);
323            } else {
324                url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + host + ":" + registryPort
325                                        + serviceUrlPath);
326            }
327            cs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server);
328    
329            // Start the connector server asynchronously (in a separate thread).
330            Thread connectorThread = new Thread() {
331                public void run() {
332                    try {
333                        cs.start();
334                    } catch (IOException ioe) {
335                        LOG.warn("Could not start jmx connector thread.", ioe);
336                    }
337                }
338            };
339            connectorThread.setName("JMX Connector Thread [" + url + "]");
340            connectorThread.start();
341            LOG.info("JMX connector thread started on " + url);
342        }
343    
344        public String getMBeanObjectDomainName() {
345            return mBeanObjectDomainName;
346        }
347    
348        public void setServer(MBeanServer value) {
349            server = value;
350        }
351    
352    }