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