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.CamelContext;
044    import org.apache.camel.CamelContextAware;
045    import org.apache.camel.impl.DefaultCamelContext;
046    import org.apache.camel.impl.ServiceSupport;
047    import org.apache.camel.spi.InstrumentationAgent;
048    import org.apache.camel.util.ObjectHelper;
049    import org.apache.commons.logging.Log;
050    import org.apache.commons.logging.LogFactory;
051    import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource;
052    import org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler;
053    
054    /**
055     * Default implementation of the Camel JMX service agent
056     */
057    public class DefaultInstrumentationAgent extends ServiceSupport implements InstrumentationAgent,
058        CamelContextAware {
059        public static final String SYSTEM_PROPERTY_JMX = "org.apache.camel.jmx";
060        public static final String SYSTEM_PROPERTY_JMX_USE_PLATFORM_MBS = SYSTEM_PROPERTY_JMX + ".usePlatformMBeanServer";
061        public static final String DEFAULT_DOMAIN = "org.apache.camel";
062        public static final String DEFAULT_HOST = "localhost";
063        public static final int DEFAULT_PORT = 1099;
064        public static final String DEFAULT_CONNECTOR_PATH = "/jmxrmi";
065        private static final transient Log LOG = LogFactory.getLog(DefaultInstrumentationAgent.class);
066    
067        private MBeanServer server;
068        private CamelContext context;
069        private Set<ObjectName> mbeans = new HashSet<ObjectName>();
070        private MetadataMBeanInfoAssembler assembler;
071        private JMXConnectorServer cs;
072        private boolean jmxEnabled;
073        private String jmxDomainName;
074        private int jmxConnectorPort;
075        private String jmxConnectorPath;
076        private boolean createConnector = true;
077        private boolean usePlatformMBeanServer;
078    
079        public DefaultInstrumentationAgent() {
080            assembler = new MetadataMBeanInfoAssembler();
081            assembler.setAttributeSource(new AnnotationJmxAttributeSource());
082        }
083    
084        public CamelContext getCamelContext() {
085            return context;
086        }
087    
088        public void setCamelContext(CamelContext camelContext) {
089            context = camelContext;
090        }
091    
092        public void setCreateConnector(boolean flag) {
093            createConnector = flag;
094        }
095    
096        public void setUsePlatformMBeanServer(boolean flag) {
097            usePlatformMBeanServer = flag;
098        }
099    
100        public void setMBeanServer(MBeanServer server) {
101            this.server = server;
102            jmxEnabled = true;
103        }
104    
105        public MBeanServer getMBeanServer() {
106            if (server == null) {
107                // The MBeanServer was not injected
108                createMBeanServer();
109            }
110            return server;
111        }
112    
113        public void register(Object obj, ObjectName name) throws JMException {
114            register(obj, name, false);
115        }
116    
117        public void register(Object obj, ObjectName name, boolean forceRegistration) throws JMException {
118            try {
119                registerMBeanWithServer(obj, name, forceRegistration);
120            } catch (NotCompliantMBeanException e) {
121                // If this is not a "normal" MBean, then try to deploy it using JMX
122                // annotations
123                ModelMBeanInfo mbi = null;
124                mbi = assembler.getMBeanInfo(obj, name.toString());
125                RequiredModelMBean mbean = (RequiredModelMBean)server.instantiate(RequiredModelMBean.class
126                    .getName());
127                mbean.setModelMBeanInfo(mbi);
128                try {
129                    mbean.setManagedResource(obj, "ObjectReference");
130                } catch (InvalidTargetObjectTypeException itotex) {
131                    throw new JMException(itotex.getMessage());
132                }
133                registerMBeanWithServer(mbean, name, forceRegistration);
134            }
135        }
136    
137        public void unregister(ObjectName name) throws JMException {
138            server.unregisterMBean(name);
139        }
140    
141        protected void doStart() throws Exception {
142            ObjectHelper.notNull(context, "camelContext");
143    
144            if (getMBeanServer() == null) {
145                // No mbean server or jmx not enabled
146                return;
147            }
148    
149            if (jmxDomainName == null) {
150                jmxDomainName = System.getProperty(SYSTEM_PROPERTY_JMX + ".domain");
151                if (jmxDomainName == null || jmxDomainName.length() == 0) {
152                    jmxDomainName = DEFAULT_DOMAIN;
153                }
154            }
155    
156            if (LOG.isDebugEnabled()) {
157                LOG.debug("Starting JMX agent on server: " + getMBeanServer());
158            }
159    
160            if (context instanceof DefaultCamelContext) {
161                DefaultCamelContext dc = (DefaultCamelContext)context;
162                InstrumentationLifecycleStrategy ls = new InstrumentationLifecycleStrategy(
163                        this, new CamelNamingStrategy(jmxDomainName));
164                dc.setLifecycleStrategy(ls);
165                ls.onContextCreate(context);
166            }
167        }
168    
169        protected void doStop() throws Exception {
170            // close JMX Connector
171            if (cs != null) {
172                try {
173                    cs.stop();
174                } catch (IOException e) {
175                    // ignore
176                }
177                cs = null;
178            }
179    
180            // Using the array to hold the busMBeans to avoid the
181            // CurrentModificationException
182            Object[] mBeans = mbeans.toArray();
183            int caught = 0;
184            for (Object name : mBeans) {
185                mbeans.remove((ObjectName)name);
186                try {
187                    unregister((ObjectName)name);
188                } catch (JMException jmex) {
189                    LOG.info("Exception unregistering MBean", jmex);
190                    caught++;
191                }
192            }
193            if (caught > 0) {
194                LOG.warn("A number of " + caught
195                         + " exceptions caught while unregistering MBeans during stop operation."
196                         + " See INFO log for details.");
197            }
198        }
199    
200        private void registerMBeanWithServer(Object obj, ObjectName name, boolean forceRegistration)
201            throws JMException {
202    
203            ObjectInstance instance = null;
204            try {
205                instance = server.registerMBean(obj, name);
206            } catch (InstanceAlreadyExistsException e) {
207                if (forceRegistration) {
208                    server.unregisterMBean(name);
209                    instance = server.registerMBean(obj, name);
210                } else {
211                    throw e;
212                }
213            }
214    
215            if (instance != null) {
216                mbeans.add(name);
217            }
218        }
219    
220        public void enableJmx() {
221            enableJmx(DEFAULT_DOMAIN, DEFAULT_CONNECTOR_PATH, DEFAULT_PORT);
222        }
223    
224        public void enableJmx(String domainName, String connectorPath, int port) {
225            jmxEnabled = true;
226            jmxDomainName = domainName;
227            jmxConnectorPath = connectorPath;
228            jmxConnectorPort = port;
229        }
230    
231        protected void createMBeanServer() {
232            String hostName = DEFAULT_HOST;
233            boolean canAccessSystemProps = true;
234            try {
235                // we'll do it this way mostly to determine if we should lookup the
236                // hostName
237                SecurityManager sm = System.getSecurityManager();
238                if (sm != null) {
239                    sm.checkPropertiesAccess();
240                }
241            } catch (SecurityException se) {
242                canAccessSystemProps = false;
243            }
244    
245            if (canAccessSystemProps) {
246                if (!jmxEnabled) {
247                    jmxEnabled = null != System.getProperty(SYSTEM_PROPERTY_JMX);
248                    if (!jmxEnabled) {
249                        // we're done here
250                        return;
251                    }
252                }
253    
254                if (jmxConnectorPort <= 0) {
255                    String portKey = SYSTEM_PROPERTY_JMX + ".port";
256                    String portValue = System.getProperty(portKey);
257                    if (portValue != null && portValue.length() > 0) {
258                        try {
259                            jmxConnectorPort = Integer.parseInt(portValue);
260                        } catch (NumberFormatException nfe) {
261                            LOG.info("Invalid port number specified via System property [" + portKey + "="
262                                     + portValue + "]. Using default: " + DEFAULT_PORT);
263                            jmxConnectorPort = DEFAULT_PORT;
264                        }
265                    }
266                }
267    
268                try {
269                    hostName = InetAddress.getLocalHost().getHostName();
270                } catch (UnknownHostException uhe) {
271                    LOG.info("Cannot determine localhost name. Using default: " + DEFAULT_PORT, uhe);
272                    hostName = DEFAULT_HOST;
273                }
274            } else {
275                jmxDomainName = jmxDomainName != null ? jmxDomainName : DEFAULT_DOMAIN;
276                jmxConnectorPort = jmxConnectorPort > 0 ? jmxConnectorPort : DEFAULT_PORT;
277                hostName = DEFAULT_HOST;
278            }
279    
280            if (!jmxEnabled) {
281                return;
282            }
283    
284            // jmx is enabled but there's no MBeanServer, so create one
285            if (Boolean.getBoolean(SYSTEM_PROPERTY_JMX_USE_PLATFORM_MBS) || usePlatformMBeanServer) {
286                server = ManagementFactory.getPlatformMBeanServer();
287            } else {
288                // jmx is enabled but there's no MBeanServer, so create one
289                List servers = MBeanServerFactory.findMBeanServer(jmxDomainName);
290                if (servers.size() == 0) {
291                    server = MBeanServerFactory.createMBeanServer(jmxDomainName);
292                } else {
293                    server = (MBeanServer)servers.get(0);
294                }
295            }
296    
297            try {
298                // Create the connector if we need
299                if (createConnector) {
300                    createJmxConnector(hostName);
301                }
302            } catch (IOException ioe) {
303                LOG.warn("Could not create and start jmx connector.", ioe);
304            }
305        }
306    
307        protected void createJmxConnector(String host) throws IOException {
308            if (jmxConnectorPort > 0) {
309                try {
310                    LocateRegistry.createRegistry(jmxConnectorPort);
311                } catch (RemoteException ex) {
312                    // The registry may had been created
313                    LocateRegistry.getRegistry(jmxConnectorPort);
314                }
315    
316                if (jmxConnectorPath == null) {
317                    jmxConnectorPath = DEFAULT_CONNECTOR_PATH;
318                }
319                // Create an RMI connector and start it
320                JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + host + ":"
321                                                      + jmxConnectorPort + jmxConnectorPath);
322                cs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server);
323    
324                // Start the connector server asynchronously (in a separate thread).
325                Thread connectorThread = new Thread() {
326                    public void run() {
327                        try {
328                            cs.start();
329                        } catch (IOException ioe) {
330                            LOG.warn("Could not start jmx connector thread.", ioe);
331                        }
332                    }
333                };
334                connectorThread.setName("JMX Connector Thread [" + url + "]");
335                connectorThread.start();
336                LOG.info("JMX connector thread started on " + url);
337            }
338        }
339    }