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