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