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