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