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