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