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