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 }