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