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