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.HashMap;
026 import java.util.List;
027 import java.util.Map;
028 import java.util.concurrent.ExecutorService;
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.remote.JMXConnectorServer;
036 import javax.management.remote.JMXConnectorServerFactory;
037 import javax.management.remote.JMXServiceURL;
038
039 import org.apache.camel.CamelContext;
040 import org.apache.camel.CamelContextAware;
041 import org.apache.camel.spi.ManagementAgent;
042 import org.apache.camel.spi.ManagementMBeanAssembler;
043 import org.apache.camel.support.ServiceSupport;
044 import org.apache.camel.util.ObjectHelper;
045 import org.slf4j.Logger;
046 import org.slf4j.LoggerFactory;
047
048 /**
049 * Default implementation of the Camel JMX service agent
050 */
051 public class DefaultManagementAgent extends ServiceSupport implements ManagementAgent, CamelContextAware {
052
053 public static final String DEFAULT_DOMAIN = "org.apache.camel";
054 public static final String DEFAULT_HOST = "localhost";
055 public static final int DEFAULT_REGISTRY_PORT = 1099;
056 public static final int DEFAULT_CONNECTION_PORT = -1;
057 public static final String DEFAULT_SERVICE_URL_PATH = "/jmxrmi/camel";
058 private static final transient Logger LOG = LoggerFactory.getLogger(DefaultManagementAgent.class);
059
060 private CamelContext camelContext;
061 private ExecutorService executorService;
062 private MBeanServer server;
063 // need a name -> actual name mapping as some servers changes the names (suc as WebSphere)
064 private final Map<ObjectName, ObjectName> mbeansRegistered = new HashMap<ObjectName, ObjectName>();
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 = true;
073 private Boolean createConnector;
074 private Boolean onlyRegisterProcessorWithCustomId;
075 private Boolean registerAlways;
076 private Boolean registerNewRoutes = true;
077
078 public DefaultManagementAgent() {
079 }
080
081 public DefaultManagementAgent(CamelContext camelContext) {
082 this.camelContext = camelContext;
083 }
084
085 protected void finalizeSettings() {
086 // TODO: System properties ought to take precedence, over configured options
087
088 if (registryPort == null) {
089 registryPort = Integer.getInteger(JmxSystemPropertyKeys.REGISTRY_PORT, DEFAULT_REGISTRY_PORT);
090 }
091 if (connectorPort == null) {
092 connectorPort = Integer.getInteger(JmxSystemPropertyKeys.CONNECTOR_PORT, DEFAULT_CONNECTION_PORT);
093 }
094 if (mBeanServerDefaultDomain == null) {
095 mBeanServerDefaultDomain = System.getProperty(JmxSystemPropertyKeys.DOMAIN, DEFAULT_DOMAIN);
096 }
097 if (mBeanObjectDomainName == null) {
098 mBeanObjectDomainName = System.getProperty(JmxSystemPropertyKeys.MBEAN_DOMAIN, DEFAULT_DOMAIN);
099 }
100 if (serviceUrlPath == null) {
101 serviceUrlPath = System.getProperty(JmxSystemPropertyKeys.SERVICE_URL_PATH, DEFAULT_SERVICE_URL_PATH);
102 }
103 if (createConnector == null) {
104 createConnector = Boolean.getBoolean(JmxSystemPropertyKeys.CREATE_CONNECTOR);
105 }
106 if (onlyRegisterProcessorWithCustomId == null) {
107 onlyRegisterProcessorWithCustomId = Boolean.getBoolean(JmxSystemPropertyKeys.ONLY_REGISTER_PROCESSOR_WITH_CUSTOM_ID);
108 }
109 // "Use platform mbean server" is true by default
110 if (System.getProperty(JmxSystemPropertyKeys.USE_PLATFORM_MBS) != null) {
111 usePlatformMBeanServer = Boolean.getBoolean(JmxSystemPropertyKeys.USE_PLATFORM_MBS);
112 }
113
114 if (System.getProperty(JmxSystemPropertyKeys.REGISTER_ALWAYS) != null) {
115 registerAlways = Boolean.getBoolean(JmxSystemPropertyKeys.REGISTER_ALWAYS);
116 }
117 if (System.getProperty(JmxSystemPropertyKeys.REGISTER_NEW_ROUTES) != null) {
118 registerNewRoutes = Boolean.getBoolean(JmxSystemPropertyKeys.REGISTER_NEW_ROUTES);
119 }
120 }
121
122 public void setRegistryPort(Integer port) {
123 registryPort = port;
124 }
125
126 public Integer getRegistryPort() {
127 return registryPort;
128 }
129
130 public void setConnectorPort(Integer port) {
131 connectorPort = port;
132 }
133
134 public Integer getConnectorPort() {
135 return connectorPort;
136 }
137
138 public void setMBeanServerDefaultDomain(String domain) {
139 mBeanServerDefaultDomain = domain;
140 }
141
142 public String getMBeanServerDefaultDomain() {
143 return mBeanServerDefaultDomain;
144 }
145
146 public void setMBeanObjectDomainName(String domainName) {
147 mBeanObjectDomainName = domainName;
148 }
149
150 public String getMBeanObjectDomainName() {
151 return mBeanObjectDomainName;
152 }
153
154 public void setServiceUrlPath(String url) {
155 serviceUrlPath = url;
156 }
157
158 public String getServiceUrlPath() {
159 return serviceUrlPath;
160 }
161
162 public void setCreateConnector(Boolean flag) {
163 createConnector = flag;
164 }
165
166 public Boolean getCreateConnector() {
167 return createConnector;
168 }
169
170 public void setUsePlatformMBeanServer(Boolean flag) {
171 usePlatformMBeanServer = flag;
172 }
173
174 public Boolean getUsePlatformMBeanServer() {
175 return usePlatformMBeanServer;
176 }
177
178 public Boolean getOnlyRegisterProcessorWithCustomId() {
179 return onlyRegisterProcessorWithCustomId;
180 }
181
182 public void setOnlyRegisterProcessorWithCustomId(Boolean onlyRegisterProcessorWithCustomId) {
183 this.onlyRegisterProcessorWithCustomId = onlyRegisterProcessorWithCustomId;
184 }
185
186 public void setMBeanServer(MBeanServer mbeanServer) {
187 server = mbeanServer;
188 }
189
190 public MBeanServer getMBeanServer() {
191 return server;
192 }
193
194 public Boolean getRegisterAlways() {
195 return registerAlways != null && registerAlways;
196 }
197
198 public void setRegisterAlways(Boolean registerAlways) {
199 this.registerAlways = registerAlways;
200 }
201
202 public Boolean getRegisterNewRoutes() {
203 return registerNewRoutes != null && registerNewRoutes;
204 }
205
206 public void setRegisterNewRoutes(Boolean registerNewRoutes) {
207 this.registerNewRoutes = registerNewRoutes;
208 }
209
210 public ExecutorService getExecutorService() {
211 return executorService;
212 }
213
214 public void setExecutorService(ExecutorService executorService) {
215 this.executorService = executorService;
216 }
217
218 public CamelContext getCamelContext() {
219 return camelContext;
220 }
221
222 public void setCamelContext(CamelContext camelContext) {
223 this.camelContext = camelContext;
224 }
225
226 public void register(Object obj, ObjectName name) throws JMException {
227 register(obj, name, false);
228 }
229
230 public void register(Object obj, ObjectName name, boolean forceRegistration) throws JMException {
231 try {
232 registerMBeanWithServer(obj, name, forceRegistration);
233 } catch (NotCompliantMBeanException e) {
234 // If this is not a "normal" MBean, then try to deploy it using JMX annotations
235 ManagementMBeanAssembler assembler = camelContext.getManagementMBeanAssembler();
236 ObjectHelper.notNull(assembler, "ManagementMBeanAssembler", camelContext);
237 Object mbean = assembler.assemble(server, obj, name);
238 // and register the mbean
239 registerMBeanWithServer(mbean, name, forceRegistration);
240 }
241 }
242
243 public void unregister(ObjectName name) throws JMException {
244 if (isRegistered(name)) {
245 server.unregisterMBean(mbeansRegistered.get(name));
246 LOG.debug("Unregistered MBean with ObjectName: {}", name);
247 }
248 mbeansRegistered.remove(name);
249 }
250
251 public boolean isRegistered(ObjectName name) {
252 return (mbeansRegistered.containsKey(name)
253 && server.isRegistered(mbeansRegistered.get(name)))
254 || server.isRegistered(name);
255 }
256
257 protected void doStart() throws Exception {
258 ObjectHelper.notNull(camelContext, "CamelContext");
259
260 // create mbean server if is has not be injected.
261 if (server == null) {
262 finalizeSettings();
263 createMBeanServer();
264 }
265
266 LOG.debug("Starting JMX agent on server: {}", getMBeanServer());
267 }
268
269 protected void doStop() throws Exception {
270 // close JMX Connector
271 if (cs != null) {
272 try {
273 cs.stop();
274 } catch (IOException e) {
275 LOG.debug("Error occurred during stopping JMXConnectorService: "
276 + cs + ". This exception will be ignored.");
277 }
278 cs = null;
279 }
280
281 if (mbeansRegistered.isEmpty()) {
282 return;
283 }
284
285 // Using the array to hold the busMBeans to avoid the CurrentModificationException
286 ObjectName[] mBeans = mbeansRegistered.keySet().toArray(new ObjectName[mbeansRegistered.size()]);
287 int caught = 0;
288 for (ObjectName name : mBeans) {
289 try {
290 unregister(name);
291 } catch (Exception e) {
292 LOG.info("Exception unregistering MBean with name " + name, e);
293 caught++;
294 }
295 }
296 if (caught > 0) {
297 LOG.warn("A number of " + caught
298 + " exceptions caught while unregistering MBeans during stop operation."
299 + " See INFO log for details.");
300 }
301 }
302
303 private void registerMBeanWithServer(Object obj, ObjectName name, boolean forceRegistration)
304 throws JMException {
305
306 // have we already registered the bean, there can be shared instances in the camel routes
307 boolean exists = isRegistered(name);
308 if (exists) {
309 if (forceRegistration) {
310 LOG.info("ForceRegistration enabled, unregistering existing MBean with ObjectName: {}", name);
311 server.unregisterMBean(name);
312 } else {
313 // okay ignore we do not want to force it and it could be a shared instance
314 LOG.debug("MBean already registered with ObjectName: {}", name);
315 }
316 }
317
318 // register bean if by force or not exists
319 ObjectInstance instance = null;
320 if (forceRegistration || !exists) {
321 LOG.trace("Registering MBean with ObjectName: {}", name);
322 instance = server.registerMBean(obj, name);
323 }
324
325 // need to use the name returned from the server as some JEE servers may modify the name
326 if (instance != null) {
327 ObjectName registeredName = instance.getObjectName();
328 LOG.debug("Registered MBean with ObjectName: {}", registeredName);
329 mbeansRegistered.put(name, registeredName);
330 }
331 }
332
333 protected void createMBeanServer() {
334 String hostName;
335 boolean canAccessSystemProps = true;
336 try {
337 // we'll do it this way mostly to determine if we should lookup the hostName
338 SecurityManager sm = System.getSecurityManager();
339 if (sm != null) {
340 sm.checkPropertiesAccess();
341 }
342 } catch (SecurityException se) {
343 canAccessSystemProps = false;
344 }
345
346 if (canAccessSystemProps) {
347 try {
348 hostName = InetAddress.getLocalHost().getHostName();
349 } catch (UnknownHostException uhe) {
350 LOG.info("Cannot determine localhost name. Using default: " + DEFAULT_REGISTRY_PORT, uhe);
351 hostName = DEFAULT_HOST;
352 }
353 } else {
354 hostName = DEFAULT_HOST;
355 }
356
357 server = findOrCreateMBeanServer();
358
359 try {
360 // Create the connector if we need
361 if (createConnector) {
362 createJmxConnector(hostName);
363 }
364 } catch (IOException ioe) {
365 LOG.warn("Could not create and start JMX connector.", ioe);
366 }
367 }
368
369 protected MBeanServer findOrCreateMBeanServer() {
370
371 // return platform mbean server if the option is specified.
372 if (usePlatformMBeanServer) {
373 return ManagementFactory.getPlatformMBeanServer();
374 }
375
376 // look for the first mbean server that has match default domain name
377 List<MBeanServer> servers = MBeanServerFactory.findMBeanServer(null);
378
379 for (MBeanServer server : servers) {
380 LOG.debug("Found MBeanServer with default domain {}", server.getDefaultDomain());
381
382 if (mBeanServerDefaultDomain.equals(server.getDefaultDomain())) {
383 return server;
384 }
385 }
386
387 // create a mbean server with the given default domain name
388 return MBeanServerFactory.createMBeanServer(mBeanServerDefaultDomain);
389 }
390
391 protected void createJmxConnector(String host) throws IOException {
392 ObjectHelper.notEmpty(serviceUrlPath, "serviceUrlPath");
393 ObjectHelper.notNull(registryPort, "registryPort");
394
395 try {
396 LocateRegistry.createRegistry(registryPort);
397 LOG.debug("Created JMXConnector RMI registry on port {}", registryPort);
398 } catch (RemoteException ex) {
399 // The registry may had been created, we could get the registry instead
400 }
401
402 // must start with leading slash
403 String path = serviceUrlPath.startsWith("/") ? serviceUrlPath : "/" + serviceUrlPath;
404 // Create an RMI connector and start it
405 final JMXServiceURL url;
406 if (connectorPort > 0) {
407 url = new JMXServiceURL("service:jmx:rmi://" + host + ":" + connectorPort + "/jndi/rmi://" + host
408 + ":" + registryPort + path);
409 } else {
410 url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + host + ":" + registryPort + path);
411 }
412
413 cs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server);
414
415 if (executorService == null) {
416 // we only need a single thread for the JMX connector
417 executorService = camelContext.getExecutorServiceManager().newSingleThreadExecutor(this, "JMXConnector: " + url);
418 }
419
420 // execute the JMX connector
421 executorService.execute(new Runnable() {
422 public void run() {
423 try {
424 cs.start();
425 } catch (IOException ioe) {
426 LOG.warn("Could not start JMXConnector thread.", ioe);
427 }
428 }
429 });
430
431 LOG.info("JMX Connector thread started and listening at: " + url);
432 }
433
434 }