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.JMException;
030 import javax.management.MBeanServer;
031 import javax.management.MBeanServerFactory;
032 import javax.management.NotCompliantMBeanException;
033 import javax.management.ObjectInstance;
034 import javax.management.ObjectName;
035 import javax.management.modelmbean.InvalidTargetObjectTypeException;
036 import javax.management.modelmbean.ModelMBeanInfo;
037 import javax.management.modelmbean.RequiredModelMBean;
038 import javax.management.remote.JMXConnectorServer;
039 import javax.management.remote.JMXConnectorServerFactory;
040 import javax.management.remote.JMXServiceURL;
041
042 import org.apache.camel.impl.ServiceSupport;
043 import org.apache.camel.spi.InstrumentationAgent;
044 import org.apache.commons.logging.Log;
045 import org.apache.commons.logging.LogFactory;
046 import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource;
047 import org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler;
048
049 /**
050 * Default implementation of the Camel JMX service agent
051 */
052 public class DefaultInstrumentationAgent extends ServiceSupport implements InstrumentationAgent {
053
054 public static final String DEFAULT_DOMAIN = "org.apache.camel";
055 public static final String DEFAULT_HOST = "localhost";
056 public static final int DEFAULT_REGISTRY_PORT = 1099;
057 public static final int DEFAULT_CONNECTION_PORT = -1;
058 public static final String DEFAULT_SERVICE_URL_PATH = "/jmxrmi/camel";
059 private static final transient Log LOG = LogFactory.getLog(DefaultInstrumentationAgent.class);
060
061 private MBeanServer server;
062 private Set<ObjectName> mbeans = new HashSet<ObjectName>();
063 private MetadataMBeanInfoAssembler assembler;
064 private JMXConnectorServer cs;
065
066 private Integer registryPort;
067 private Integer connectorPort;
068 private String mBeanServerDefaultDomain;
069 private String mBeanObjectDomainName;
070 private String serviceUrlPath;
071 private Boolean usePlatformMBeanServer = true;
072 private Boolean createConnector;
073
074 protected void finalizeSettings() {
075 if (registryPort == null) {
076 registryPort = Integer.getInteger(JmxSystemPropertyKeys.REGISTRY_PORT,
077 DEFAULT_REGISTRY_PORT);
078 }
079
080 if (connectorPort == null) {
081 connectorPort = Integer.getInteger(JmxSystemPropertyKeys.CONNECTOR_PORT,
082 DEFAULT_CONNECTION_PORT);
083 }
084
085 if (mBeanServerDefaultDomain == null) {
086 mBeanServerDefaultDomain =
087 System.getProperty(JmxSystemPropertyKeys.DOMAIN, DEFAULT_DOMAIN);
088 }
089
090 if (mBeanObjectDomainName == null) {
091 mBeanObjectDomainName =
092 System.getProperty(JmxSystemPropertyKeys.MBEAN_DOMAIN, DEFAULT_DOMAIN);
093 }
094
095 if (serviceUrlPath == null) {
096 serviceUrlPath =
097 System.getProperty(JmxSystemPropertyKeys.SERVICE_URL_PATH,
098 DEFAULT_SERVICE_URL_PATH);
099 }
100
101 if (createConnector == null) {
102 createConnector = Boolean.getBoolean(JmxSystemPropertyKeys.CREATE_CONNECTOR);
103 }
104
105 // "Use platform mbean server" is true by default
106 if (System.getProperty(JmxSystemPropertyKeys.USE_PLATFORM_MBS) != null) {
107 usePlatformMBeanServer = Boolean.getBoolean(JmxSystemPropertyKeys.USE_PLATFORM_MBS);
108 }
109 }
110
111
112 public void setRegistryPort(Integer value) {
113 registryPort = value;
114 }
115
116 public void setConnectorPort(Integer value) {
117 connectorPort = value;
118 }
119
120 public void setMBeanServerDefaultDomain(String value) {
121 mBeanServerDefaultDomain = value;
122 }
123
124 public void setMBeanObjectDomainName(String value) {
125 mBeanObjectDomainName = value;
126 }
127
128 public void setServiceUrlPath(String value) {
129 serviceUrlPath = value;
130 }
131
132 public void setCreateConnector(Boolean flag) {
133 createConnector = flag;
134 }
135
136 public void setUsePlatformMBeanServer(Boolean flag) {
137 usePlatformMBeanServer = flag;
138 }
139
140 public MBeanServer getMBeanServer() {
141 return server;
142 }
143
144 public void register(Object obj, ObjectName name) throws JMException {
145 register(obj, name, false);
146 }
147
148 public void register(Object obj, ObjectName name, boolean forceRegistration) throws JMException {
149 try {
150 registerMBeanWithServer(obj, name, forceRegistration);
151 } catch (NotCompliantMBeanException e) {
152 // If this is not a "normal" MBean, then try to deploy it using JMX
153 // annotations
154 ModelMBeanInfo mbi = null;
155 mbi = assembler.getMBeanInfo(obj, name.toString());
156 RequiredModelMBean mbean = (RequiredModelMBean)server.instantiate(RequiredModelMBean.class
157 .getName());
158 mbean.setModelMBeanInfo(mbi);
159 try {
160 mbean.setManagedResource(obj, "ObjectReference");
161 } catch (InvalidTargetObjectTypeException itotex) {
162 throw new JMException(itotex.getMessage());
163 }
164 registerMBeanWithServer(mbean, name, forceRegistration);
165 }
166 }
167
168 public void unregister(ObjectName name) throws JMException {
169 server.unregisterMBean(name);
170 }
171
172 protected void doStart() throws Exception {
173 assembler = new MetadataMBeanInfoAssembler();
174 assembler.setAttributeSource(new AnnotationJmxAttributeSource());
175
176 // create mbean server if is has not be injected.
177 if (server == null) {
178 finalizeSettings();
179 createMBeanServer();
180 }
181
182 if (LOG.isDebugEnabled()) {
183 LOG.debug("Starting JMX agent on server: " + getMBeanServer());
184 }
185 }
186
187 protected void doStop() throws Exception {
188 // close JMX Connector
189 if (cs != null) {
190 try {
191 cs.stop();
192 } catch (IOException e) {
193 // ignore
194 }
195 cs = null;
196 }
197
198 // Using the array to hold the busMBeans to avoid the
199 // CurrentModificationException
200 Object[] mBeans = mbeans.toArray();
201 int caught = 0;
202 for (Object name : mBeans) {
203 mbeans.remove((ObjectName)name);
204 try {
205 unregister((ObjectName)name);
206 } catch (JMException jmex) {
207 LOG.info("Exception unregistering MBean", jmex);
208 caught++;
209 }
210 }
211 if (caught > 0) {
212 LOG.warn("A number of " + caught
213 + " exceptions caught while unregistering MBeans during stop operation."
214 + " See INFO log for details.");
215 }
216 }
217
218 private void registerMBeanWithServer(Object obj, ObjectName name, boolean forceRegistration)
219 throws JMException {
220
221 // have we already registered the bean, there can be shared instances in the camel routes
222 boolean exists = server.isRegistered(name);
223 if (exists) {
224 if (forceRegistration) {
225 LOG.info("ForceRegistration enabled, unregistering existing MBean");
226 server.unregisterMBean(name);
227 } else {
228 // okay ignore we do not want to force it and it could be a shared instance
229 if (LOG.isDebugEnabled()) {
230 LOG.debug("MBean already registered with objectname: " + name);
231 }
232 }
233 }
234
235 // register bean if by force or not exsists
236 ObjectInstance instance = null;
237 if (forceRegistration || !exists) {
238 if (LOG.isTraceEnabled()) {
239 LOG.trace("Registering MBean with objectname: " + name);
240 }
241 instance = server.registerMBean(obj, name);
242 }
243
244 if (instance != null) {
245 ObjectName registeredName = instance.getObjectName();
246 if (LOG.isDebugEnabled()) {
247 LOG.debug("Registered MBean with objectname: " + registeredName);
248 }
249
250 mbeans.add(registeredName);
251 }
252 }
253
254 protected void createMBeanServer() {
255 String hostName = DEFAULT_HOST;
256 boolean canAccessSystemProps = true;
257 try {
258 // we'll do it this way mostly to determine if we should lookup the
259 // hostName
260 SecurityManager sm = System.getSecurityManager();
261 if (sm != null) {
262 sm.checkPropertiesAccess();
263 }
264 } catch (SecurityException se) {
265 canAccessSystemProps = false;
266 }
267
268 if (canAccessSystemProps) {
269 try {
270 hostName = InetAddress.getLocalHost().getHostName();
271 } catch (UnknownHostException uhe) {
272 LOG.info("Cannot determine localhost name. Using default: "
273 + DEFAULT_REGISTRY_PORT, uhe);
274 hostName = DEFAULT_HOST;
275 }
276 } else {
277 hostName = DEFAULT_HOST;
278 }
279
280 server = findOrCreateMBeanServer();
281
282 try {
283 // Create the connector if we need
284 if (createConnector) {
285 createJmxConnector(hostName);
286 }
287 } catch (IOException ioe) {
288 LOG.warn("Could not create and start JMX connector.", ioe);
289 }
290 }
291
292 @SuppressWarnings("unchecked")
293 protected MBeanServer findOrCreateMBeanServer() {
294
295 // return platform mbean server if the option is specified.
296 if (usePlatformMBeanServer) {
297 return ManagementFactory.getPlatformMBeanServer();
298 }
299
300 // look for the first mbean server that has match default domain name
301 List<MBeanServer> servers =
302 (List<MBeanServer>)MBeanServerFactory.findMBeanServer(null);
303
304 for (MBeanServer server : servers) {
305 if (LOG.isDebugEnabled()) {
306 LOG.debug("Found MBeanServer with default domain " + server.getDefaultDomain());
307 }
308
309 if (mBeanServerDefaultDomain.equals(server.getDefaultDomain())) {
310 return server;
311 }
312 }
313
314 // create a mbean server with the given default domain name
315 return MBeanServerFactory.createMBeanServer(mBeanServerDefaultDomain);
316 }
317
318 protected void createJmxConnector(String host) throws IOException {
319 try {
320 LocateRegistry.createRegistry(registryPort);
321 if (LOG.isDebugEnabled()) {
322 LOG.debug("Created JMXConnector RMI regisry on port " + registryPort);
323 }
324 } catch (RemoteException ex) {
325 // The registry may had been created, we could get the registry instead
326 }
327
328 // Create an RMI connector and start it
329 JMXServiceURL url;
330
331 if (connectorPort > 0) {
332 url = new JMXServiceURL("service:jmx:rmi://" + host + ":" + connectorPort + "/jndi/rmi://" + host
333 + ":" + registryPort + serviceUrlPath);
334 } else {
335 url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + host + ":" + registryPort
336 + serviceUrlPath);
337 }
338 cs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server);
339
340 // Start the connector server asynchronously (in a separate thread).
341 Thread connectorThread = new Thread() {
342 public void run() {
343 try {
344 cs.start();
345 } catch (IOException ioe) {
346 LOG.warn("Could not start JMXConnector thread.", ioe);
347 }
348 }
349 };
350 connectorThread.setName("Camel JMX Connector Thread [" + url + "]");
351 connectorThread.start();
352 LOG.info("JMX Connector thread started and listening at: " + url);
353 }
354
355 public String getMBeanObjectDomainName() {
356 return mBeanObjectDomainName;
357 }
358
359 public void setServer(MBeanServer value) {
360 server = value;
361 }
362
363 }