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     */
017    package org.apache.camel.management;
018    
019    import java.io.IOException;
020    import java.lang.management.ManagementFactory;
021    import java.net.InetAddress;
022    import java.net.UnknownHostException;
023    import java.rmi.RemoteException;
024    import java.rmi.registry.LocateRegistry;
025    import java.util.HashMap;
026    import java.util.List;
027    import java.util.Map;
028    import java.util.concurrent.ExecutorService;
029    import javax.management.JMException;
030    import javax.management.MBeanServer;
031    import javax.management.MBeanServerFactory;
032    import javax.management.NotCompliantMBeanException;
033    import javax.management.ObjectInstance;
034    import javax.management.ObjectName;
035    import javax.management.remote.JMXConnectorServer;
036    import javax.management.remote.JMXConnectorServerFactory;
037    import javax.management.remote.JMXServiceURL;
038    
039    import org.apache.camel.CamelContext;
040    import org.apache.camel.CamelContextAware;
041    import org.apache.camel.spi.ManagementAgent;
042    import org.apache.camel.spi.ManagementMBeanAssembler;
043    import org.apache.camel.support.ServiceSupport;
044    import org.apache.camel.util.ObjectHelper;
045    import org.slf4j.Logger;
046    import org.slf4j.LoggerFactory;
047    
048    /**
049     * Default implementation of the Camel JMX service agent
050     */
051    public 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        public static final int DEFAULT_REGISTRY_PORT = 1099;
056        public static final int DEFAULT_CONNECTION_PORT = -1;
057        public static final String DEFAULT_SERVICE_URL_PATH = "/jmxrmi/camel";
058        private static final transient Logger LOG = LoggerFactory.getLogger(DefaultManagementAgent.class);
059    
060        private CamelContext camelContext;
061        private ExecutorService executorService;
062        private MBeanServer server;
063        // need a name -> actual name mapping as some servers changes the names (suc as WebSphere)
064        private final Map<ObjectName, ObjectName> mbeansRegistered = new HashMap<ObjectName, ObjectName>();
065        private JMXConnectorServer cs;
066    
067        private Integer registryPort;
068        private Integer connectorPort;
069        private String mBeanServerDefaultDomain;
070        private String mBeanObjectDomainName;
071        private String serviceUrlPath;
072        private Boolean usePlatformMBeanServer = true;
073        private Boolean createConnector;
074        private Boolean onlyRegisterProcessorWithCustomId;
075        private Boolean registerAlways;
076        private Boolean registerNewRoutes = true;
077    
078        public DefaultManagementAgent() {
079        }
080    
081        public DefaultManagementAgent(CamelContext camelContext) {
082            this.camelContext = camelContext;
083        }
084    
085        protected void finalizeSettings() {
086            // TODO: System properties ought to take precedence, over configured options
087    
088            if (registryPort == null) {
089                registryPort = Integer.getInteger(JmxSystemPropertyKeys.REGISTRY_PORT, DEFAULT_REGISTRY_PORT);
090            }
091            if (connectorPort == null) {
092                connectorPort = Integer.getInteger(JmxSystemPropertyKeys.CONNECTOR_PORT, DEFAULT_CONNECTION_PORT);
093            }
094            if (mBeanServerDefaultDomain == null) {
095                mBeanServerDefaultDomain = System.getProperty(JmxSystemPropertyKeys.DOMAIN, DEFAULT_DOMAIN);
096            }
097            if (mBeanObjectDomainName == null) {
098                mBeanObjectDomainName = System.getProperty(JmxSystemPropertyKeys.MBEAN_DOMAIN, DEFAULT_DOMAIN);
099            }
100            if (serviceUrlPath == null) {
101                serviceUrlPath = System.getProperty(JmxSystemPropertyKeys.SERVICE_URL_PATH, DEFAULT_SERVICE_URL_PATH);
102            }
103            if (createConnector == null) {
104                createConnector = Boolean.getBoolean(JmxSystemPropertyKeys.CREATE_CONNECTOR);
105            }
106            if (onlyRegisterProcessorWithCustomId == null) {
107                onlyRegisterProcessorWithCustomId = Boolean.getBoolean(JmxSystemPropertyKeys.ONLY_REGISTER_PROCESSOR_WITH_CUSTOM_ID);
108            }
109            // "Use platform mbean server" is true by default
110            if (System.getProperty(JmxSystemPropertyKeys.USE_PLATFORM_MBS) != null) {
111                usePlatformMBeanServer = Boolean.getBoolean(JmxSystemPropertyKeys.USE_PLATFORM_MBS);
112            }
113    
114            if (System.getProperty(JmxSystemPropertyKeys.REGISTER_ALWAYS) != null) {
115                registerAlways = Boolean.getBoolean(JmxSystemPropertyKeys.REGISTER_ALWAYS);
116            }
117            if (System.getProperty(JmxSystemPropertyKeys.REGISTER_NEW_ROUTES) != null) {
118                registerNewRoutes = Boolean.getBoolean(JmxSystemPropertyKeys.REGISTER_NEW_ROUTES);
119            }
120        }
121    
122        public void setRegistryPort(Integer port) {
123            registryPort = port;
124        }
125    
126        public Integer getRegistryPort() {
127            return registryPort;
128        }
129    
130        public void setConnectorPort(Integer port) {
131            connectorPort = port;
132        }
133    
134        public Integer getConnectorPort() {
135            return connectorPort;
136        }
137    
138        public void setMBeanServerDefaultDomain(String domain) {
139            mBeanServerDefaultDomain = domain;
140        }
141    
142        public String getMBeanServerDefaultDomain() {
143            return mBeanServerDefaultDomain;
144        }
145    
146        public void setMBeanObjectDomainName(String domainName) {
147            mBeanObjectDomainName = domainName;
148        }
149    
150        public String getMBeanObjectDomainName() {
151            return mBeanObjectDomainName;
152        }
153    
154        public void setServiceUrlPath(String url) {
155            serviceUrlPath = url;
156        }
157    
158        public String getServiceUrlPath() {
159            return serviceUrlPath;
160        }
161    
162        public void setCreateConnector(Boolean flag) {
163            createConnector = flag;
164        }
165    
166        public Boolean getCreateConnector() {
167            return createConnector;
168        }
169    
170        public void setUsePlatformMBeanServer(Boolean flag) {
171            usePlatformMBeanServer = flag;
172        }
173    
174        public Boolean getUsePlatformMBeanServer() {
175            return usePlatformMBeanServer;
176        }
177    
178        public Boolean getOnlyRegisterProcessorWithCustomId() {
179            return onlyRegisterProcessorWithCustomId;
180        }
181    
182        public void setOnlyRegisterProcessorWithCustomId(Boolean onlyRegisterProcessorWithCustomId) {
183            this.onlyRegisterProcessorWithCustomId = onlyRegisterProcessorWithCustomId;
184        }
185    
186        public void setMBeanServer(MBeanServer mbeanServer) {
187            server = mbeanServer;
188        }
189    
190        public MBeanServer getMBeanServer() {
191            return server;
192        }
193    
194        public Boolean getRegisterAlways() {
195            return registerAlways != null && registerAlways;
196        }
197    
198        public void setRegisterAlways(Boolean registerAlways) {
199            this.registerAlways = registerAlways;
200        }
201    
202        public Boolean getRegisterNewRoutes() {
203            return registerNewRoutes != null && registerNewRoutes;
204        }
205    
206        public void setRegisterNewRoutes(Boolean registerNewRoutes) {
207            this.registerNewRoutes = registerNewRoutes;
208        }
209    
210        public ExecutorService getExecutorService() {
211            return executorService;
212        }
213    
214        public void setExecutorService(ExecutorService executorService) {
215            this.executorService = executorService;
216        }
217    
218        public CamelContext getCamelContext() {
219            return camelContext;
220        }
221    
222        public void setCamelContext(CamelContext camelContext) {
223            this.camelContext = camelContext;
224        }
225    
226        public void register(Object obj, ObjectName name) throws JMException {
227            register(obj, name, false);
228        }
229    
230        public void register(Object obj, ObjectName name, boolean forceRegistration) throws JMException {
231            try {
232                registerMBeanWithServer(obj, name, forceRegistration);
233            } catch (NotCompliantMBeanException e) {
234                // If this is not a "normal" MBean, then try to deploy it using JMX annotations
235                ManagementMBeanAssembler assembler = camelContext.getManagementMBeanAssembler();
236                ObjectHelper.notNull(assembler, "ManagementMBeanAssembler", camelContext);
237                Object mbean = assembler.assemble(server, obj, name);
238                // and register the mbean
239                registerMBeanWithServer(mbean, name, forceRegistration);
240            }
241        }
242    
243        public void unregister(ObjectName name) throws JMException {
244            if (isRegistered(name)) {
245                server.unregisterMBean(mbeansRegistered.get(name));
246                LOG.debug("Unregistered MBean with ObjectName: {}", name);
247            }
248            mbeansRegistered.remove(name);
249        }
250    
251        public boolean isRegistered(ObjectName name) {
252            return (mbeansRegistered.containsKey(name) 
253                    && server.isRegistered(mbeansRegistered.get(name))) 
254                    || server.isRegistered(name);
255        }
256    
257        protected void doStart() throws Exception {
258            ObjectHelper.notNull(camelContext, "CamelContext");
259    
260            // create mbean server if is has not be injected.
261            if (server == null) {
262                finalizeSettings();
263                createMBeanServer();
264            }
265    
266            LOG.debug("Starting JMX agent on server: {}", getMBeanServer());
267        }
268    
269        protected void doStop() throws Exception {
270            // close JMX Connector
271            if (cs != null) {
272                try {
273                    cs.stop();
274                } catch (IOException e) {
275                    LOG.debug("Error occurred during stopping JMXConnectorService: "
276                            + cs + ". This exception will be ignored.");
277                }
278                cs = null;
279            }
280    
281            if (mbeansRegistered.isEmpty()) {
282                return;
283            }
284    
285            // Using the array to hold the busMBeans to avoid the CurrentModificationException
286            ObjectName[] mBeans = mbeansRegistered.keySet().toArray(new ObjectName[mbeansRegistered.size()]);
287            int caught = 0;
288            for (ObjectName name : mBeans) {
289                try {
290                    unregister(name);
291                } catch (Exception e) {
292                    LOG.info("Exception unregistering MBean with name " + name, e);
293                    caught++;
294                }
295            }
296            if (caught > 0) {
297                LOG.warn("A number of " + caught
298                         + " exceptions caught while unregistering MBeans during stop operation."
299                         + " See INFO log for details.");
300            }
301        }
302    
303        private void registerMBeanWithServer(Object obj, ObjectName name, boolean forceRegistration)
304            throws JMException {
305    
306            // have we already registered the bean, there can be shared instances in the camel routes
307            boolean exists = isRegistered(name);
308            if (exists) {
309                if (forceRegistration) {
310                    LOG.info("ForceRegistration enabled, unregistering existing MBean with ObjectName: {}", name);
311                    server.unregisterMBean(name);
312                } else {
313                    // okay ignore we do not want to force it and it could be a shared instance
314                    LOG.debug("MBean already registered with ObjectName: {}", name);
315                }
316            }
317    
318            // register bean if by force or not exists
319            ObjectInstance instance = null;
320            if (forceRegistration || !exists) {
321                LOG.trace("Registering MBean with ObjectName: {}", name);
322                instance = server.registerMBean(obj, name);
323            }
324    
325            // need to use the name returned from the server as some JEE servers may modify the name
326            if (instance != null) {
327                ObjectName registeredName = instance.getObjectName();
328                LOG.debug("Registered MBean with ObjectName: {}", registeredName);
329                mbeansRegistered.put(name, registeredName);
330            }
331        }
332    
333        protected void createMBeanServer() {
334            String hostName;
335            boolean canAccessSystemProps = true;
336            try {
337                // we'll do it this way mostly to determine if we should lookup the hostName
338                SecurityManager sm = System.getSecurityManager();
339                if (sm != null) {
340                    sm.checkPropertiesAccess();
341                }
342            } catch (SecurityException se) {
343                canAccessSystemProps = false;
344            }
345    
346            if (canAccessSystemProps) {
347                try {
348                    hostName = InetAddress.getLocalHost().getHostName();
349                } catch (UnknownHostException uhe) {
350                    LOG.info("Cannot determine localhost name. Using default: " + DEFAULT_REGISTRY_PORT, uhe);
351                    hostName = DEFAULT_HOST;
352                }
353            } else {
354                hostName = DEFAULT_HOST;
355            }
356    
357            server = findOrCreateMBeanServer();
358    
359            try {
360                // Create the connector if we need
361                if (createConnector) {
362                    createJmxConnector(hostName);
363                }
364            } catch (IOException ioe) {
365                LOG.warn("Could not create and start JMX connector.", ioe);
366            }
367        }
368        
369        protected MBeanServer findOrCreateMBeanServer() {
370    
371            // return platform mbean server if the option is specified.
372            if (usePlatformMBeanServer) {
373                return ManagementFactory.getPlatformMBeanServer();
374            }
375    
376            // look for the first mbean server that has match default domain name
377            List<MBeanServer> servers = MBeanServerFactory.findMBeanServer(null);
378    
379            for (MBeanServer server : servers) {
380                LOG.debug("Found MBeanServer with default domain {}", server.getDefaultDomain());
381    
382                if (mBeanServerDefaultDomain.equals(server.getDefaultDomain())) {
383                    return server;
384                }
385            }
386    
387            // create a mbean server with the given default domain name
388            return MBeanServerFactory.createMBeanServer(mBeanServerDefaultDomain);
389        }
390    
391        protected void createJmxConnector(String host) throws IOException {
392            ObjectHelper.notEmpty(serviceUrlPath, "serviceUrlPath");
393            ObjectHelper.notNull(registryPort, "registryPort");
394    
395            try {
396                LocateRegistry.createRegistry(registryPort);
397                LOG.debug("Created JMXConnector RMI registry on port {}", registryPort);
398            } catch (RemoteException ex) {
399                // The registry may had been created, we could get the registry instead
400            }
401    
402            // must start with leading slash
403            String path = serviceUrlPath.startsWith("/") ? serviceUrlPath : "/" + serviceUrlPath;
404            // Create an RMI connector and start it
405            final JMXServiceURL url;
406            if (connectorPort > 0) {
407                url = new JMXServiceURL("service:jmx:rmi://" + host + ":" + connectorPort + "/jndi/rmi://" + host
408                                        + ":" + registryPort + path);
409            } else {
410                url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + host + ":" + registryPort + path);
411            }
412    
413            cs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server);
414    
415            if (executorService == null) {
416                // we only need a single thread for the JMX connector
417                executorService = camelContext.getExecutorServiceManager().newSingleThreadExecutor(this, "JMXConnector: " + url);
418            }
419    
420            // execute the JMX connector
421            executorService.execute(new Runnable() {
422                public void run() {
423                    try {
424                        cs.start();
425                    } catch (IOException ioe) {
426                        LOG.warn("Could not start JMXConnector thread.", ioe);
427                    }
428                }
429            });
430    
431            LOG.info("JMX Connector thread started and listening at: " + url);
432        }
433    
434    }