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 */
017package org.apache.camel.management;
018
019import java.io.IOException;
020import java.lang.management.ManagementFactory;
021import java.net.InetAddress;
022import java.net.UnknownHostException;
023import java.rmi.NoSuchObjectException;
024import java.rmi.RemoteException;
025import java.rmi.registry.LocateRegistry;
026import java.rmi.registry.Registry;
027import java.rmi.server.UnicastRemoteObject;
028import java.util.LinkedHashMap;
029import java.util.List;
030import java.util.Map;
031import java.util.concurrent.ConcurrentHashMap;
032import java.util.concurrent.ConcurrentMap;
033
034import javax.management.JMException;
035import javax.management.MBeanServer;
036import javax.management.MBeanServerFactory;
037import javax.management.MBeanServerInvocationHandler;
038import javax.management.NotCompliantMBeanException;
039import javax.management.ObjectInstance;
040import javax.management.ObjectName;
041import javax.management.remote.JMXConnectorServer;
042import javax.management.remote.JMXConnectorServerFactory;
043import javax.management.remote.JMXServiceURL;
044
045import org.apache.camel.CamelContext;
046import org.apache.camel.CamelContextAware;
047import org.apache.camel.ManagementStatisticsLevel;
048import org.apache.camel.api.management.JmxSystemPropertyKeys;
049import org.apache.camel.spi.ManagementAgent;
050import org.apache.camel.spi.ManagementMBeanAssembler;
051import org.apache.camel.support.management.DefaultManagementMBeanAssembler;
052import org.apache.camel.support.service.ServiceHelper;
053import org.apache.camel.support.service.ServiceSupport;
054import org.apache.camel.util.InetAddressUtil;
055import org.apache.camel.util.ObjectHelper;
056import org.apache.camel.util.StringHelper;
057import org.slf4j.Logger;
058import org.slf4j.LoggerFactory;
059
060/**
061 * Default implementation of the Camel JMX service agent
062 */
063public class DefaultManagementAgent extends ServiceSupport implements ManagementAgent, CamelContextAware {
064
065    public static final String DEFAULT_DOMAIN = "org.apache.camel";
066    public static final String DEFAULT_HOST = "localhost";
067    public static final int DEFAULT_REGISTRY_PORT = 1099;
068    public static final int DEFAULT_CONNECTION_PORT = -1;
069    public static final String DEFAULT_SERVICE_URL_PATH = "/jmxrmi/camel";
070    private static final Logger LOG = LoggerFactory.getLogger(DefaultManagementAgent.class);
071
072    private CamelContext camelContext;
073    private MBeanServer server;
074    private ManagementMBeanAssembler assembler;
075
076    // need a name -> actual name mapping as some servers changes the names (such as WebSphere)
077    private final ConcurrentMap<ObjectName, ObjectName> mbeansRegistered = new ConcurrentHashMap<>();
078    private JMXConnectorServer cs;
079    private Registry registry;
080
081    private Integer registryPort = DEFAULT_REGISTRY_PORT;
082    private Integer connectorPort = DEFAULT_CONNECTION_PORT;
083    private String mBeanServerDefaultDomain = DEFAULT_DOMAIN;
084    private String mBeanObjectDomainName = DEFAULT_DOMAIN;
085    private String serviceUrlPath = DEFAULT_SERVICE_URL_PATH;
086    private Boolean usePlatformMBeanServer = true;
087    private Boolean createConnector = false;
088    private Boolean onlyRegisterProcessorWithCustomId = false;
089    private Boolean loadStatisticsEnabled = false;
090    private Boolean endpointRuntimeStatisticsEnabled;
091    private Boolean registerAlways = false;
092    private Boolean registerNewRoutes = true;
093    private Boolean mask = true;
094    private Boolean includeHostName = false;
095    private Boolean useHostIPAddress = false;
096    private String managementNamePattern = "#name#";
097    private ManagementStatisticsLevel statisticsLevel = ManagementStatisticsLevel.Default;
098
099    public DefaultManagementAgent() {
100    }
101
102    public DefaultManagementAgent(CamelContext camelContext) {
103        this.camelContext = camelContext;
104    }
105
106    protected void finalizeSettings() throws Exception {
107        // JVM system properties take precedence over any configuration
108        Map<String, Object> values = new LinkedHashMap<>();
109
110        if (System.getProperty(JmxSystemPropertyKeys.REGISTRY_PORT) != null) {
111            registryPort = Integer.getInteger(JmxSystemPropertyKeys.REGISTRY_PORT);
112            values.put(JmxSystemPropertyKeys.REGISTRY_PORT, registryPort);
113        }
114        if (System.getProperty(JmxSystemPropertyKeys.CONNECTOR_PORT) != null) {
115            connectorPort = Integer.getInteger(JmxSystemPropertyKeys.CONNECTOR_PORT);
116            values.put(JmxSystemPropertyKeys.CONNECTOR_PORT, connectorPort);
117        }
118        if (System.getProperty(JmxSystemPropertyKeys.DOMAIN) != null) {
119            mBeanServerDefaultDomain = System.getProperty(JmxSystemPropertyKeys.DOMAIN);
120            values.put(JmxSystemPropertyKeys.DOMAIN, mBeanServerDefaultDomain);
121        }
122        if (System.getProperty(JmxSystemPropertyKeys.MBEAN_DOMAIN) != null) {
123            mBeanObjectDomainName = System.getProperty(JmxSystemPropertyKeys.MBEAN_DOMAIN);
124            values.put(JmxSystemPropertyKeys.MBEAN_DOMAIN, mBeanObjectDomainName);
125        }
126        if (System.getProperty(JmxSystemPropertyKeys.SERVICE_URL_PATH) != null) {
127            serviceUrlPath = System.getProperty(JmxSystemPropertyKeys.SERVICE_URL_PATH);
128            values.put(JmxSystemPropertyKeys.SERVICE_URL_PATH, serviceUrlPath);
129        }
130        if (System.getProperty(JmxSystemPropertyKeys.CREATE_CONNECTOR) != null) {
131            createConnector = Boolean.getBoolean(JmxSystemPropertyKeys.CREATE_CONNECTOR);
132            values.put(JmxSystemPropertyKeys.CREATE_CONNECTOR, createConnector);
133        }
134        if (System.getProperty(JmxSystemPropertyKeys.ONLY_REGISTER_PROCESSOR_WITH_CUSTOM_ID) != null) {
135            onlyRegisterProcessorWithCustomId = Boolean.getBoolean(JmxSystemPropertyKeys.ONLY_REGISTER_PROCESSOR_WITH_CUSTOM_ID);
136            values.put(JmxSystemPropertyKeys.ONLY_REGISTER_PROCESSOR_WITH_CUSTOM_ID, onlyRegisterProcessorWithCustomId);
137        }
138        if (System.getProperty(JmxSystemPropertyKeys.USE_PLATFORM_MBS) != null) {
139            usePlatformMBeanServer = Boolean.getBoolean(JmxSystemPropertyKeys.USE_PLATFORM_MBS);
140            values.put(JmxSystemPropertyKeys.USE_PLATFORM_MBS, usePlatformMBeanServer);
141        }
142        if (System.getProperty(JmxSystemPropertyKeys.REGISTER_ALWAYS) != null) {
143            registerAlways = Boolean.getBoolean(JmxSystemPropertyKeys.REGISTER_ALWAYS);
144            values.put(JmxSystemPropertyKeys.REGISTER_ALWAYS, registerAlways);
145        }
146        if (System.getProperty(JmxSystemPropertyKeys.REGISTER_NEW_ROUTES) != null) {
147            registerNewRoutes = Boolean.getBoolean(JmxSystemPropertyKeys.REGISTER_NEW_ROUTES);
148            values.put(JmxSystemPropertyKeys.REGISTER_NEW_ROUTES, registerNewRoutes);
149        }
150        if (System.getProperty(JmxSystemPropertyKeys.MASK) != null) {
151            mask = Boolean.getBoolean(JmxSystemPropertyKeys.MASK);
152            values.put(JmxSystemPropertyKeys.MASK, mask);
153        }
154        if (System.getProperty(JmxSystemPropertyKeys.INCLUDE_HOST_NAME) != null) {
155            includeHostName = Boolean.getBoolean(JmxSystemPropertyKeys.INCLUDE_HOST_NAME);
156            values.put(JmxSystemPropertyKeys.INCLUDE_HOST_NAME, includeHostName);
157        }
158        if (System.getProperty(JmxSystemPropertyKeys.CREATE_CONNECTOR) != null) {
159            createConnector = Boolean.getBoolean(JmxSystemPropertyKeys.CREATE_CONNECTOR);
160            values.put(JmxSystemPropertyKeys.CREATE_CONNECTOR, createConnector);
161        }
162        if (System.getProperty(JmxSystemPropertyKeys.LOAD_STATISTICS_ENABLED) != null) {
163            loadStatisticsEnabled = Boolean.getBoolean(JmxSystemPropertyKeys.LOAD_STATISTICS_ENABLED);
164            values.put(JmxSystemPropertyKeys.LOAD_STATISTICS_ENABLED, loadStatisticsEnabled);
165        }
166        if (System.getProperty(JmxSystemPropertyKeys.ENDPOINT_RUNTIME_STATISTICS_ENABLED) != null) {
167            endpointRuntimeStatisticsEnabled = Boolean.getBoolean(JmxSystemPropertyKeys.ENDPOINT_RUNTIME_STATISTICS_ENABLED);
168            values.put(JmxSystemPropertyKeys.ENDPOINT_RUNTIME_STATISTICS_ENABLED, endpointRuntimeStatisticsEnabled);
169        }
170        if (System.getProperty(JmxSystemPropertyKeys.STATISTICS_LEVEL) != null) {
171            statisticsLevel = camelContext.getTypeConverter().mandatoryConvertTo(ManagementStatisticsLevel.class, System.getProperty(JmxSystemPropertyKeys.STATISTICS_LEVEL));
172            values.put(JmxSystemPropertyKeys.STATISTICS_LEVEL, statisticsLevel);
173        }
174        if (System.getProperty(JmxSystemPropertyKeys.MANAGEMENT_NAME_PATTERN) != null) {
175            managementNamePattern = System.getProperty(JmxSystemPropertyKeys.MANAGEMENT_NAME_PATTERN);
176            values.put(JmxSystemPropertyKeys.MANAGEMENT_NAME_PATTERN, managementNamePattern);
177        }
178        if (System.getProperty(JmxSystemPropertyKeys.USE_HOST_IP_ADDRESS) != null) {
179            useHostIPAddress = Boolean.getBoolean(JmxSystemPropertyKeys.USE_HOST_IP_ADDRESS);
180            values.put(JmxSystemPropertyKeys.USE_HOST_IP_ADDRESS, useHostIPAddress);
181        }
182
183        if (!values.isEmpty()) {
184            LOG.info("ManagementAgent detected JVM system properties: {}", values);
185        }
186    }
187
188    public void setRegistryPort(Integer port) {
189        registryPort = port;
190    }
191
192    public Integer getRegistryPort() {
193        return registryPort;
194    }
195
196    public void setConnectorPort(Integer port) {
197        connectorPort = port;
198    }
199
200    public Integer getConnectorPort() {
201        return connectorPort;
202    }
203
204    public void setMBeanServerDefaultDomain(String domain) {
205        mBeanServerDefaultDomain = domain;
206    }
207
208    public String getMBeanServerDefaultDomain() {
209        return mBeanServerDefaultDomain;
210    }
211
212    public void setMBeanObjectDomainName(String domainName) {
213        mBeanObjectDomainName = domainName;
214    }
215
216    public String getMBeanObjectDomainName() {
217        return mBeanObjectDomainName;
218    }
219
220    public void setServiceUrlPath(String url) {
221        serviceUrlPath = url;
222    }
223
224    public String getServiceUrlPath() {
225        return serviceUrlPath;
226    }
227
228    public void setCreateConnector(Boolean flag) {
229        createConnector = flag;
230    }
231
232    public Boolean getCreateConnector() {
233        return createConnector;
234    }
235
236    public void setUsePlatformMBeanServer(Boolean flag) {
237        usePlatformMBeanServer = flag;
238    }
239
240    public Boolean getUsePlatformMBeanServer() {
241        return usePlatformMBeanServer;
242    }
243
244    public Boolean getOnlyRegisterProcessorWithCustomId() {
245        return onlyRegisterProcessorWithCustomId;
246    }
247
248    public void setOnlyRegisterProcessorWithCustomId(Boolean onlyRegisterProcessorWithCustomId) {
249        this.onlyRegisterProcessorWithCustomId = onlyRegisterProcessorWithCustomId;
250    }
251
252    public void setMBeanServer(MBeanServer mbeanServer) {
253        server = mbeanServer;
254    }
255
256    public MBeanServer getMBeanServer() {
257        return server;
258    }
259
260    public Boolean getRegisterAlways() {
261        return registerAlways != null && registerAlways;
262    }
263
264    public void setRegisterAlways(Boolean registerAlways) {
265        this.registerAlways = registerAlways;
266    }
267
268    public Boolean getRegisterNewRoutes() {
269        return registerNewRoutes != null && registerNewRoutes;
270    }
271
272    public void setRegisterNewRoutes(Boolean registerNewRoutes) {
273        this.registerNewRoutes = registerNewRoutes;
274    }
275
276    public Boolean getMask() {
277        return mask != null && mask;
278    }
279
280    public void setMask(Boolean mask) {
281        this.mask = mask;
282    }
283
284    public Boolean getIncludeHostName() {
285        return includeHostName != null && includeHostName;
286    }
287
288    public void setIncludeHostName(Boolean includeHostName) {
289        this.includeHostName = includeHostName;
290    }
291
292    public Boolean getUseHostIPAddress() {
293        return useHostIPAddress != null && useHostIPAddress;
294    }
295
296    public void setUseHostIPAddress(Boolean useHostIPAddress) {
297        this.useHostIPAddress = useHostIPAddress;
298    }
299
300    public String getManagementNamePattern() {
301        return managementNamePattern;
302    }
303
304    public void setManagementNamePattern(String managementNamePattern) {
305        this.managementNamePattern = managementNamePattern;
306    }
307
308    public Boolean getLoadStatisticsEnabled() {
309        return loadStatisticsEnabled;
310    }
311
312    public void setLoadStatisticsEnabled(Boolean loadStatisticsEnabled) {
313        this.loadStatisticsEnabled = loadStatisticsEnabled;
314    }
315
316    public Boolean getEndpointRuntimeStatisticsEnabled() {
317        return endpointRuntimeStatisticsEnabled;
318    }
319
320    public void setEndpointRuntimeStatisticsEnabled(Boolean endpointRuntimeStatisticsEnabled) {
321        this.endpointRuntimeStatisticsEnabled = endpointRuntimeStatisticsEnabled;
322    }
323
324    public ManagementStatisticsLevel getStatisticsLevel() {
325        return statisticsLevel;
326    }
327
328    public void setStatisticsLevel(ManagementStatisticsLevel statisticsLevel) {
329        this.statisticsLevel = statisticsLevel;
330    }
331
332    public CamelContext getCamelContext() {
333        return camelContext;
334    }
335
336    public void setCamelContext(CamelContext camelContext) {
337        this.camelContext = camelContext;
338    }
339
340    public void register(Object obj, ObjectName name) throws JMException {
341        register(obj, name, false);
342    }
343
344    public void register(Object obj, ObjectName name, boolean forceRegistration) throws JMException {
345        try {
346            registerMBeanWithServer(obj, name, forceRegistration);
347        } catch (NotCompliantMBeanException e) {
348            // If this is not a "normal" MBean, then try to deploy it using JMX annotations
349            ObjectHelper.notNull(assembler, "ManagementMBeanAssembler", camelContext);
350            Object mbean = assembler.assemble(server, obj, name);
351            if (mbean != null) {
352                // and register the mbean
353                registerMBeanWithServer(mbean, name, forceRegistration);
354            }
355        }
356    }
357
358    public void unregister(ObjectName name) throws JMException {
359        if (isRegistered(name)) {
360            ObjectName on = mbeansRegistered.remove(name);
361            server.unregisterMBean(on);
362            LOG.debug("Unregistered MBean with ObjectName: {}", name);
363        } else {
364            mbeansRegistered.remove(name);
365        }
366    }
367
368    public boolean isRegistered(ObjectName name) {
369        if (server == null) {
370            return false;
371        }
372        ObjectName on = mbeansRegistered.get(name);
373        return (on != null && server.isRegistered(on))
374                || server.isRegistered(name);
375    }
376
377    public <T> T newProxyClient(ObjectName name, Class<T> mbean) {
378        if (isRegistered(name)) {
379            ObjectName on = mbeansRegistered.get(name);
380            return MBeanServerInvocationHandler.newProxyInstance(server, on != null ? on : name, mbean, false);
381        } else {
382            return null;
383        }
384    }
385
386    protected void doStart() throws Exception {
387        ObjectHelper.notNull(camelContext, "CamelContext");
388
389        // create mbean server if is has not be injected.
390        if (server == null) {
391            finalizeSettings();
392            createMBeanServer();
393        }
394
395        // ensure assembler is started
396        assembler = camelContext.getManagementMBeanAssembler();
397        if (assembler == null) {
398            assembler = new DefaultManagementMBeanAssembler(camelContext);
399        }
400        ServiceHelper.startService(assembler);
401
402        LOG.debug("Starting JMX agent on server: {}", getMBeanServer());
403    }
404
405    protected void doStop() throws Exception {
406        // close JMX Connector, if it was created
407        if (cs != null) {
408            try {
409                cs.stop();
410                LOG.debug("Stopped JMX Connector");
411            } catch (IOException e) {
412                LOG.debug("Error occurred during stopping JMXConnectorService: "
413                        + cs + ". This exception will be ignored.");
414            }
415            cs = null;
416        }
417
418        // Unexport JMX RMI registry, if it was created
419        if (registry != null) {
420            try {
421                UnicastRemoteObject.unexportObject(registry, true);
422                LOG.debug("Unexported JMX RMI Registry");
423            } catch (NoSuchObjectException e) {
424                LOG.debug("Error occurred while unexporting JMX RMI registry. This exception will be ignored.");
425            }
426        }
427
428        if (mbeansRegistered.isEmpty()) {
429            return;
430        }
431
432        // Using the array to hold the busMBeans to avoid the CurrentModificationException
433        ObjectName[] mBeans = mbeansRegistered.keySet().toArray(new ObjectName[mbeansRegistered.size()]);
434        int caught = 0;
435        for (ObjectName name : mBeans) {
436            try {
437                unregister(name);
438            } catch (Exception e) {
439                LOG.info("Exception unregistering MBean with name {}", name, e);
440                caught++;
441            }
442        }
443        if (caught > 0) {
444            LOG.warn("A number of " + caught
445                     + " exceptions caught while unregistering MBeans during stop operation."
446                     + " See INFO log for details.");
447        }
448
449        ServiceHelper.stopService(assembler);
450    }
451
452    private void registerMBeanWithServer(Object obj, ObjectName name, boolean forceRegistration)
453        throws JMException {
454
455        // have we already registered the bean, there can be shared instances in the camel routes
456        boolean exists = isRegistered(name);
457        if (exists) {
458            if (forceRegistration) {
459                LOG.info("ForceRegistration enabled, unregistering existing MBean with ObjectName: {}", name);
460                server.unregisterMBean(name);
461            } else {
462                // okay ignore we do not want to force it and it could be a shared instance
463                LOG.debug("MBean already registered with ObjectName: {}", name);
464            }
465        }
466
467        // register bean if by force or not exists
468        ObjectInstance instance = null;
469        if (forceRegistration || !exists) {
470            LOG.trace("Registering MBean with ObjectName: {}", name);
471            instance = server.registerMBean(obj, name);
472        }
473
474        // need to use the name returned from the server as some JEE servers may modify the name
475        if (instance != null) {
476            ObjectName registeredName = instance.getObjectName();
477            LOG.debug("Registered MBean with ObjectName: {}", registeredName);
478            mbeansRegistered.put(name, registeredName);
479        }
480    }
481
482    protected void createMBeanServer() {
483        String hostName;
484        boolean canAccessSystemProps = true;
485        try {
486            // we'll do it this way mostly to determine if we should lookup the hostName
487            SecurityManager sm = System.getSecurityManager();
488            if (sm != null) {
489                sm.checkPropertiesAccess();
490            }
491        } catch (SecurityException se) {
492            canAccessSystemProps = false;
493        }
494
495        if (canAccessSystemProps) {
496            try {
497                if (useHostIPAddress) {
498                    hostName = InetAddress.getLocalHost().getHostAddress();
499                } else {
500                    hostName = InetAddressUtil.getLocalHostName();
501                }
502            } catch (UnknownHostException uhe) {
503                LOG.info("Cannot determine localhost name or address. Using default: {}", DEFAULT_REGISTRY_PORT, uhe);
504                hostName = DEFAULT_HOST;
505            }
506        } else {
507            hostName = DEFAULT_HOST;
508        }
509
510        server = findOrCreateMBeanServer();
511
512        try {
513            // Create the connector if we need
514            if (createConnector) {
515                createJmxConnector(hostName);
516            }
517        } catch (IOException ioe) {
518            LOG.warn("Could not create and start JMX connector.", ioe);
519        }
520    }
521    
522    protected MBeanServer findOrCreateMBeanServer() {
523
524        // return platform mbean server if the option is specified.
525        if (usePlatformMBeanServer) {
526            return ManagementFactory.getPlatformMBeanServer();
527        }
528
529        // look for the first mbean server that has match default domain name
530        List<MBeanServer> servers = MBeanServerFactory.findMBeanServer(null);
531
532        for (MBeanServer server : servers) {
533            LOG.debug("Found MBeanServer with default domain {}", server.getDefaultDomain());
534
535            if (mBeanServerDefaultDomain.equals(server.getDefaultDomain())) {
536                return server;
537            }
538        }
539
540        // create a mbean server with the given default domain name
541        return MBeanServerFactory.createMBeanServer(mBeanServerDefaultDomain);
542    }
543
544    protected void createJmxConnector(String host) throws IOException {
545        StringHelper.notEmpty(serviceUrlPath, "serviceUrlPath");
546        ObjectHelper.notNull(registryPort, "registryPort");
547
548        try {
549            registry = LocateRegistry.createRegistry(registryPort);
550            LOG.debug("Created JMXConnector RMI registry on port {}", registryPort);
551        } catch (RemoteException ex) {
552            // The registry may had been created, we could get the registry instead
553        }
554
555        // must start with leading slash
556        String path = serviceUrlPath.startsWith("/") ? serviceUrlPath : "/" + serviceUrlPath;
557        // Create an RMI connector and start it
558        final JMXServiceURL url;
559        if (connectorPort > 0) {
560            url = new JMXServiceURL("service:jmx:rmi://" + host + ":" + connectorPort + "/jndi/rmi://" + host
561                                    + ":" + registryPort + path);
562        } else {
563            url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + host + ":" + registryPort + path);
564        }
565
566        cs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server);
567
568        // use async thread for starting the JMX Connector
569        // (no need to use a thread pool or enlist in JMX as this thread is terminated when the JMX connector has been started)
570        String threadName = camelContext.getExecutorServiceManager().resolveThreadName("JMXConnector: " + url);
571        Thread thread = getCamelContext().getExecutorServiceManager().newThread(threadName, new Runnable() {
572            public void run() {
573                try {
574                    LOG.debug("Staring JMX Connector thread to listen at: {}", url);
575                    cs.start();
576                    LOG.info("JMX Connector thread started and listening at: {}", url);
577                } catch (IOException ioe) {
578                    LOG.warn("Could not start JMXConnector thread at: " + url + ". JMX Connector not in use.", ioe);
579                }
580            }
581        });
582        thread.start();
583    }
584
585}