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 }