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