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