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.lang.management.ManagementFactory;
020import java.util.LinkedHashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.concurrent.ConcurrentHashMap;
024import java.util.concurrent.ConcurrentMap;
025
026import javax.management.JMException;
027import javax.management.MBeanServer;
028import javax.management.MBeanServerFactory;
029import javax.management.MBeanServerInvocationHandler;
030import javax.management.NotCompliantMBeanException;
031import javax.management.ObjectInstance;
032import javax.management.ObjectName;
033
034import org.apache.camel.CamelContext;
035import org.apache.camel.CamelContextAware;
036import org.apache.camel.ManagementMBeansLevel;
037import org.apache.camel.ManagementStatisticsLevel;
038import org.apache.camel.api.management.JmxSystemPropertyKeys;
039import org.apache.camel.spi.ManagementAgent;
040import org.apache.camel.spi.ManagementMBeanAssembler;
041import org.apache.camel.support.management.DefaultManagementMBeanAssembler;
042import org.apache.camel.support.service.ServiceHelper;
043import org.apache.camel.support.service.ServiceSupport;
044import org.apache.camel.util.ObjectHelper;
045import org.slf4j.Logger;
046import org.slf4j.LoggerFactory;
047
048/**
049 * Default implementation of the Camel JMX service agent
050 */
051public 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    private static final Logger LOG = LoggerFactory.getLogger(DefaultManagementAgent.class);
056
057    private CamelContext camelContext;
058    private MBeanServer server;
059    private ManagementMBeanAssembler assembler;
060
061    // need a name -> actual name mapping as some servers changes the names (such as WebSphere)
062    private final ConcurrentMap<ObjectName, ObjectName> mbeansRegistered = new ConcurrentHashMap<>();
063
064    private String mBeanServerDefaultDomain = DEFAULT_DOMAIN;
065    private String mBeanObjectDomainName = DEFAULT_DOMAIN;
066    private Boolean usePlatformMBeanServer = true;
067    private Boolean onlyRegisterProcessorWithCustomId = false;
068    private Boolean loadStatisticsEnabled = false;
069    private Boolean endpointRuntimeStatisticsEnabled;
070    private Boolean registerAlways = false;
071    private Boolean registerNewRoutes = true;
072    private Boolean registerRoutesCreateByKamelet = false;
073    private Boolean registerRoutesCreateByTemplate = true;
074    private Boolean mask = true;
075    private Boolean includeHostName = false;
076    private Boolean useHostIPAddress = false;
077    private Boolean updateRouteEnabled = false;
078    private String managementNamePattern = "#name#";
079    private ManagementStatisticsLevel statisticsLevel = ManagementStatisticsLevel.Default;
080    private ManagementMBeansLevel mBeansLevel = ManagementMBeansLevel.Default;
081
082    public DefaultManagementAgent() {
083    }
084
085    public DefaultManagementAgent(CamelContext camelContext) {
086        this.camelContext = camelContext;
087    }
088
089    protected void finalizeSettings() throws Exception {
090        // JVM system properties take precedence over any configuration
091        Map<String, Object> values = new LinkedHashMap<>();
092
093        if (System.getProperty(JmxSystemPropertyKeys.DOMAIN) != null) {
094            mBeanServerDefaultDomain = System.getProperty(JmxSystemPropertyKeys.DOMAIN);
095            values.put(JmxSystemPropertyKeys.DOMAIN, mBeanServerDefaultDomain);
096        }
097        if (System.getProperty(JmxSystemPropertyKeys.MBEAN_DOMAIN) != null) {
098            mBeanObjectDomainName = System.getProperty(JmxSystemPropertyKeys.MBEAN_DOMAIN);
099            values.put(JmxSystemPropertyKeys.MBEAN_DOMAIN, mBeanObjectDomainName);
100        }
101        if (System.getProperty(JmxSystemPropertyKeys.ONLY_REGISTER_PROCESSOR_WITH_CUSTOM_ID) != null) {
102            onlyRegisterProcessorWithCustomId
103                    = Boolean.getBoolean(JmxSystemPropertyKeys.ONLY_REGISTER_PROCESSOR_WITH_CUSTOM_ID);
104            values.put(JmxSystemPropertyKeys.ONLY_REGISTER_PROCESSOR_WITH_CUSTOM_ID, onlyRegisterProcessorWithCustomId);
105        }
106        if (System.getProperty(JmxSystemPropertyKeys.USE_PLATFORM_MBS) != null) {
107            usePlatformMBeanServer = Boolean.getBoolean(JmxSystemPropertyKeys.USE_PLATFORM_MBS);
108            values.put(JmxSystemPropertyKeys.USE_PLATFORM_MBS, usePlatformMBeanServer);
109        }
110        if (System.getProperty(JmxSystemPropertyKeys.REGISTER_ALWAYS) != null) {
111            registerAlways = Boolean.getBoolean(JmxSystemPropertyKeys.REGISTER_ALWAYS);
112            values.put(JmxSystemPropertyKeys.REGISTER_ALWAYS, registerAlways);
113        }
114        if (System.getProperty(JmxSystemPropertyKeys.REGISTER_NEW_ROUTES) != null) {
115            registerNewRoutes = Boolean.getBoolean(JmxSystemPropertyKeys.REGISTER_NEW_ROUTES);
116            values.put(JmxSystemPropertyKeys.REGISTER_NEW_ROUTES, registerNewRoutes);
117        }
118        if (System.getProperty(JmxSystemPropertyKeys.REGISTER_ROUTES_CREATED_BY_TEMPLATE) != null) {
119            registerRoutesCreateByTemplate = Boolean.getBoolean(JmxSystemPropertyKeys.REGISTER_ROUTES_CREATED_BY_TEMPLATE);
120            values.put(JmxSystemPropertyKeys.REGISTER_ROUTES_CREATED_BY_TEMPLATE, registerRoutesCreateByTemplate);
121        }
122        if (System.getProperty(JmxSystemPropertyKeys.REGISTER_ROUTES_CREATED_BY_KAMELET) != null) {
123            registerRoutesCreateByKamelet = Boolean.getBoolean(JmxSystemPropertyKeys.REGISTER_ROUTES_CREATED_BY_KAMELET);
124            values.put(JmxSystemPropertyKeys.REGISTER_ROUTES_CREATED_BY_KAMELET, registerRoutesCreateByKamelet);
125        }
126        if (System.getProperty(JmxSystemPropertyKeys.MASK) != null) {
127            mask = Boolean.getBoolean(JmxSystemPropertyKeys.MASK);
128            values.put(JmxSystemPropertyKeys.MASK, mask);
129        }
130        if (System.getProperty(JmxSystemPropertyKeys.INCLUDE_HOST_NAME) != null) {
131            includeHostName = Boolean.getBoolean(JmxSystemPropertyKeys.INCLUDE_HOST_NAME);
132            values.put(JmxSystemPropertyKeys.INCLUDE_HOST_NAME, includeHostName);
133        }
134        if (System.getProperty(JmxSystemPropertyKeys.LOAD_STATISTICS_ENABLED) != null) {
135            loadStatisticsEnabled = Boolean.getBoolean(JmxSystemPropertyKeys.LOAD_STATISTICS_ENABLED);
136            values.put(JmxSystemPropertyKeys.LOAD_STATISTICS_ENABLED, loadStatisticsEnabled);
137        }
138        if (System.getProperty(JmxSystemPropertyKeys.ENDPOINT_RUNTIME_STATISTICS_ENABLED) != null) {
139            endpointRuntimeStatisticsEnabled = Boolean.getBoolean(JmxSystemPropertyKeys.ENDPOINT_RUNTIME_STATISTICS_ENABLED);
140            values.put(JmxSystemPropertyKeys.ENDPOINT_RUNTIME_STATISTICS_ENABLED, endpointRuntimeStatisticsEnabled);
141        }
142        if (System.getProperty(JmxSystemPropertyKeys.STATISTICS_LEVEL) != null) {
143            statisticsLevel = camelContext.getTypeConverter().mandatoryConvertTo(ManagementStatisticsLevel.class,
144                    System.getProperty(JmxSystemPropertyKeys.STATISTICS_LEVEL));
145            values.put(JmxSystemPropertyKeys.STATISTICS_LEVEL, statisticsLevel);
146        }
147        if (System.getProperty(JmxSystemPropertyKeys.MANAGEMENT_NAME_PATTERN) != null) {
148            managementNamePattern = System.getProperty(JmxSystemPropertyKeys.MANAGEMENT_NAME_PATTERN);
149            values.put(JmxSystemPropertyKeys.MANAGEMENT_NAME_PATTERN, managementNamePattern);
150        }
151        if (System.getProperty(JmxSystemPropertyKeys.USE_HOST_IP_ADDRESS) != null) {
152            useHostIPAddress = Boolean.getBoolean(JmxSystemPropertyKeys.USE_HOST_IP_ADDRESS);
153            values.put(JmxSystemPropertyKeys.USE_HOST_IP_ADDRESS, useHostIPAddress);
154        }
155        if (System.getProperty(JmxSystemPropertyKeys.UPDATE_ROUTE_ENABLED) != null) {
156            updateRouteEnabled = Boolean.getBoolean(JmxSystemPropertyKeys.UPDATE_ROUTE_ENABLED);
157            values.put(JmxSystemPropertyKeys.UPDATE_ROUTE_ENABLED, updateRouteEnabled);
158        }
159
160        if (!values.isEmpty()) {
161            LOG.info("ManagementAgent detected JVM system properties: {}", values);
162        }
163    }
164
165    @Override
166    public void setMBeanServerDefaultDomain(String domain) {
167        mBeanServerDefaultDomain = domain;
168    }
169
170    @Override
171    public String getMBeanServerDefaultDomain() {
172        return mBeanServerDefaultDomain;
173    }
174
175    @Override
176    public void setMBeanObjectDomainName(String domainName) {
177        mBeanObjectDomainName = domainName;
178    }
179
180    @Override
181    public String getMBeanObjectDomainName() {
182        return mBeanObjectDomainName;
183    }
184
185    @Override
186    public void setUsePlatformMBeanServer(Boolean flag) {
187        usePlatformMBeanServer = flag;
188    }
189
190    @Override
191    public Boolean getUsePlatformMBeanServer() {
192        return usePlatformMBeanServer;
193    }
194
195    @Override
196    public Boolean getOnlyRegisterProcessorWithCustomId() {
197        return onlyRegisterProcessorWithCustomId;
198    }
199
200    @Override
201    public void setOnlyRegisterProcessorWithCustomId(Boolean onlyRegisterProcessorWithCustomId) {
202        this.onlyRegisterProcessorWithCustomId = onlyRegisterProcessorWithCustomId;
203    }
204
205    @Override
206    public void setMBeanServer(MBeanServer mbeanServer) {
207        server = mbeanServer;
208    }
209
210    @Override
211    public MBeanServer getMBeanServer() {
212        return server;
213    }
214
215    @Override
216    public Boolean getRegisterAlways() {
217        return registerAlways != null && registerAlways;
218    }
219
220    @Override
221    public void setRegisterAlways(Boolean registerAlways) {
222        this.registerAlways = registerAlways;
223    }
224
225    @Override
226    public Boolean getRegisterNewRoutes() {
227        return registerNewRoutes != null && registerNewRoutes;
228    }
229
230    @Override
231    public void setRegisterNewRoutes(Boolean registerNewRoutes) {
232        this.registerNewRoutes = registerNewRoutes;
233    }
234
235    public Boolean getRegisterRoutesCreateByKamelet() {
236        return registerRoutesCreateByKamelet != null && registerRoutesCreateByKamelet;
237    }
238
239    public void setRegisterRoutesCreateByKamelet(Boolean registerRoutesCreateByKamelet) {
240        this.registerRoutesCreateByKamelet = registerRoutesCreateByKamelet;
241    }
242
243    public Boolean getRegisterRoutesCreateByTemplate() {
244        return registerRoutesCreateByTemplate != null && registerRoutesCreateByTemplate;
245    }
246
247    public void setRegisterRoutesCreateByTemplate(Boolean registerRoutesCreateByTemplate) {
248        this.registerRoutesCreateByTemplate = registerRoutesCreateByTemplate;
249    }
250
251    @Override
252    public Boolean getMask() {
253        return mask != null && mask;
254    }
255
256    @Override
257    public void setMask(Boolean mask) {
258        this.mask = mask;
259    }
260
261    @Override
262    public Boolean getIncludeHostName() {
263        return includeHostName != null && includeHostName;
264    }
265
266    @Override
267    public void setIncludeHostName(Boolean includeHostName) {
268        this.includeHostName = includeHostName;
269    }
270
271    @Override
272    public Boolean getUseHostIPAddress() {
273        return useHostIPAddress != null && useHostIPAddress;
274    }
275
276    @Override
277    public void setUseHostIPAddress(Boolean useHostIPAddress) {
278        this.useHostIPAddress = useHostIPAddress;
279    }
280
281    @Override
282    public String getManagementNamePattern() {
283        return managementNamePattern;
284    }
285
286    @Override
287    public void setManagementNamePattern(String managementNamePattern) {
288        this.managementNamePattern = managementNamePattern;
289    }
290
291    @Override
292    public Boolean getLoadStatisticsEnabled() {
293        return loadStatisticsEnabled;
294    }
295
296    @Override
297    public void setLoadStatisticsEnabled(Boolean loadStatisticsEnabled) {
298        this.loadStatisticsEnabled = loadStatisticsEnabled;
299    }
300
301    @Override
302    public Boolean getEndpointRuntimeStatisticsEnabled() {
303        return endpointRuntimeStatisticsEnabled;
304    }
305
306    @Override
307    public void setEndpointRuntimeStatisticsEnabled(Boolean endpointRuntimeStatisticsEnabled) {
308        this.endpointRuntimeStatisticsEnabled = endpointRuntimeStatisticsEnabled;
309    }
310
311    @Override
312    public ManagementStatisticsLevel getStatisticsLevel() {
313        return statisticsLevel;
314    }
315
316    @Override
317    public void setStatisticsLevel(ManagementStatisticsLevel statisticsLevel) {
318        this.statisticsLevel = statisticsLevel;
319    }
320
321    @Override
322    public ManagementMBeansLevel getMBeansLevel() {
323        return mBeansLevel;
324    }
325
326    @Override
327    public void setMBeansLevel(ManagementMBeansLevel mBeansLevel) {
328        this.mBeansLevel = mBeansLevel;
329    }
330
331    @Override
332    public Boolean getUpdateRouteEnabled() {
333        return updateRouteEnabled != null && updateRouteEnabled;
334    }
335
336    @Override
337    public void setUpdateRouteEnabled(Boolean updateRouteEnabled) {
338        this.updateRouteEnabled = updateRouteEnabled;
339    }
340
341    @Override
342    public CamelContext getCamelContext() {
343        return camelContext;
344    }
345
346    @Override
347    public void setCamelContext(CamelContext camelContext) {
348        this.camelContext = camelContext;
349    }
350
351    @Override
352    public void register(Object obj, ObjectName name) throws JMException {
353        register(obj, name, false);
354    }
355
356    @Override
357    public void register(Object obj, ObjectName name, boolean forceRegistration) throws JMException {
358        try {
359            registerMBeanWithServer(obj, name, forceRegistration);
360        } catch (NotCompliantMBeanException e) {
361            // If this is not a "normal" MBean, then try to deploy it using JMX annotations
362            ObjectHelper.notNull(assembler, "ManagementMBeanAssembler", camelContext);
363            Object mbean = assembler.assemble(server, obj, name);
364            if (mbean != null) {
365                // and register the mbean
366                registerMBeanWithServer(mbean, name, forceRegistration);
367            }
368        }
369    }
370
371    @Override
372    public void unregister(ObjectName name) throws JMException {
373        if (isRegistered(name)) {
374            ObjectName on = mbeansRegistered.remove(name);
375            server.unregisterMBean(on);
376            LOG.debug("Unregistered MBean with ObjectName: {}", name);
377        } else {
378            mbeansRegistered.remove(name);
379        }
380    }
381
382    @Override
383    public boolean isRegistered(ObjectName name) {
384        if (server == null) {
385            return false;
386        }
387        ObjectName on = mbeansRegistered.get(name);
388        return on != null && server.isRegistered(on)
389                || server.isRegistered(name);
390    }
391
392    @Override
393    public <T> T newProxyClient(ObjectName name, Class<T> mbean) {
394        if (isRegistered(name)) {
395            ObjectName on = mbeansRegistered.get(name);
396            return MBeanServerInvocationHandler.newProxyInstance(server, on != null ? on : name, mbean, false);
397        } else {
398            return null;
399        }
400    }
401
402    @Override
403    protected void doInit() throws Exception {
404        ObjectHelper.notNull(camelContext, "CamelContext");
405
406        finalizeSettings();
407
408        assembler = camelContext.getCamelContextExtension().getManagementMBeanAssembler();
409        if (assembler == null) {
410            assembler = new DefaultManagementMBeanAssembler(camelContext);
411        }
412        ServiceHelper.initService(assembler);
413    }
414
415    @Override
416    protected void doStart() throws Exception {
417        // create mbean server if is has not be injected.
418        if (server == null) {
419            createMBeanServer();
420        }
421
422        // ensure assembler is started
423        ServiceHelper.startService(assembler);
424
425        LOG.debug("Starting JMX agent on server: {}", getMBeanServer());
426    }
427
428    @Override
429    protected void doStop() throws Exception {
430        if (mbeansRegistered.isEmpty()) {
431            return;
432        }
433
434        // Using the array to hold the busMBeans to avoid the CurrentModificationException
435        ObjectName[] mBeans = mbeansRegistered.keySet().toArray(new ObjectName[0]);
436        int caught = 0;
437        for (ObjectName name : mBeans) {
438            try {
439                unregister(name);
440            } catch (Exception e) {
441                LOG.info("Exception unregistering MBean with name {}", name, e);
442                caught++;
443            }
444        }
445        if (caught > 0) {
446            LOG.warn("{} exceptions caught while unregistering MBeans during stop operation. See INFO log for details.",
447                    caught);
448        }
449
450        ServiceHelper.stopService(assembler);
451    }
452
453    private void registerMBeanWithServer(Object obj, ObjectName name, boolean forceRegistration)
454            throws JMException {
455
456        // have we already registered the bean, there can be shared instances in the camel routes
457        boolean exists = isRegistered(name);
458        if (exists) {
459            if (forceRegistration) {
460                LOG.info("ForceRegistration enabled, unregistering existing MBean with ObjectName: {}", name);
461                server.unregisterMBean(name);
462            } else {
463                // okay ignore we do not want to force it and it could be a shared instance
464                LOG.debug("MBean already registered with ObjectName: {}", name);
465            }
466        }
467
468        // register bean if by force or not exists
469        ObjectInstance instance = null;
470        if (forceRegistration || !exists) {
471            LOG.trace("Registering MBean with ObjectName: {}", name);
472            instance = server.registerMBean(obj, name);
473        }
474
475        // need to use the name returned from the server as some JEE servers may modify the name
476        if (instance != null) {
477            ObjectName registeredName = instance.getObjectName();
478            LOG.debug("Registered MBean with ObjectName: {}", registeredName);
479            mbeansRegistered.put(name, registeredName);
480        }
481    }
482
483    protected void createMBeanServer() {
484        server = findOrCreateMBeanServer();
485    }
486
487    protected MBeanServer findOrCreateMBeanServer() {
488
489        // return platform mbean server if the option is specified.
490        if (usePlatformMBeanServer) {
491            return ManagementFactory.getPlatformMBeanServer();
492        }
493
494        // look for the first mbean server that has match default domain name
495        List<MBeanServer> servers = MBeanServerFactory.findMBeanServer(null);
496
497        for (MBeanServer server : servers) {
498            LOG.debug("Found MBeanServer with default domain {}", server.getDefaultDomain());
499
500            if (mBeanServerDefaultDomain.equals(server.getDefaultDomain())) {
501                return server;
502            }
503        }
504
505        // create a mbean server with the given default domain name
506        return MBeanServerFactory.createMBeanServer(mBeanServerDefaultDomain);
507    }
508
509}