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.HashSet;
026    import java.util.List;
027    import java.util.Set;
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.modelmbean.InvalidTargetObjectTypeException;
036    import javax.management.modelmbean.ModelMBeanInfo;
037    import javax.management.modelmbean.RequiredModelMBean;
038    import javax.management.remote.JMXConnectorServer;
039    import javax.management.remote.JMXConnectorServerFactory;
040    import javax.management.remote.JMXServiceURL;
041    
042    import org.apache.camel.impl.ServiceSupport;
043    import org.apache.camel.spi.InstrumentationAgent;
044    import org.apache.camel.util.concurrent.ExecutorServiceHelper;
045    import org.apache.commons.logging.Log;
046    import org.apache.commons.logging.LogFactory;
047    import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource;
048    import org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler;
049    
050    /**
051     * Default implementation of the Camel JMX service agent
052     */
053    public class DefaultInstrumentationAgent extends ServiceSupport implements InstrumentationAgent {
054    
055        public static final String DEFAULT_DOMAIN = "org.apache.camel";
056        public static final String DEFAULT_HOST = "localhost";
057        public static final int DEFAULT_REGISTRY_PORT = 1099;
058        public static final int DEFAULT_CONNECTION_PORT = -1;
059        public static final String DEFAULT_SERVICE_URL_PATH = "/jmxrmi/camel";
060        private static final transient Log LOG = LogFactory.getLog(DefaultInstrumentationAgent.class);
061    
062        private ExecutorService executorService;
063        private MBeanServer server;
064        private Set<ObjectName> mbeans = new HashSet<ObjectName>();
065        private MetadataMBeanInfoAssembler assembler;
066        private JMXConnectorServer cs;
067    
068        private Integer registryPort;
069        private Integer connectorPort;
070        private String mBeanServerDefaultDomain;
071        private String mBeanObjectDomainName;
072        private String serviceUrlPath;
073        private Boolean usePlatformMBeanServer = true;
074        private Boolean createConnector;
075        private Boolean onlyRegisterProcessorWithCustomId;
076    
077        protected void finalizeSettings() {
078            if (registryPort == null) {
079                registryPort = Integer.getInteger(JmxSystemPropertyKeys.REGISTRY_PORT, DEFAULT_REGISTRY_PORT);
080            }
081    
082            if (connectorPort == null) {
083                connectorPort = Integer.getInteger(JmxSystemPropertyKeys.CONNECTOR_PORT, DEFAULT_CONNECTION_PORT);
084            }
085    
086            if (mBeanServerDefaultDomain == null) {
087                mBeanServerDefaultDomain = System.getProperty(JmxSystemPropertyKeys.DOMAIN, DEFAULT_DOMAIN);
088            }
089    
090            if (mBeanObjectDomainName == null) {
091                mBeanObjectDomainName = System.getProperty(JmxSystemPropertyKeys.MBEAN_DOMAIN, DEFAULT_DOMAIN);
092            }
093    
094            if (serviceUrlPath == null) {
095                serviceUrlPath = System.getProperty(JmxSystemPropertyKeys.SERVICE_URL_PATH, DEFAULT_SERVICE_URL_PATH);
096            }
097    
098            if (createConnector == null) {
099                createConnector = Boolean.getBoolean(JmxSystemPropertyKeys.CREATE_CONNECTOR);
100            }
101    
102            // "Use platform mbean server" is true by default
103            if (System.getProperty(JmxSystemPropertyKeys.USE_PLATFORM_MBS) != null) {
104                usePlatformMBeanServer = Boolean.getBoolean(JmxSystemPropertyKeys.USE_PLATFORM_MBS);
105            }
106    
107            if (onlyRegisterProcessorWithCustomId == null) {
108                onlyRegisterProcessorWithCustomId = Boolean.getBoolean(JmxSystemPropertyKeys.ONLY_REGISTER_PROCESSOR_WITH_CUSTOM_ID);
109            }
110        }
111    
112        public void setRegistryPort(Integer value) {
113            registryPort = value;
114        }
115    
116        public Integer getRegistryPort() {
117            return registryPort;
118        }
119    
120        public void setConnectorPort(Integer value) {
121            connectorPort = value;
122        }
123    
124        public Integer getConnectorPort() {
125            return connectorPort;
126        }
127    
128        public void setMBeanServerDefaultDomain(String value) {
129            mBeanServerDefaultDomain = value;
130        }
131    
132        public String getMBeanServerDefaultDomain() {
133            return mBeanServerDefaultDomain;
134        }
135    
136        public void setMBeanObjectDomainName(String value) {
137            mBeanObjectDomainName = value;
138        }
139    
140        public String getMBeanObjectDomainName() {
141            return mBeanObjectDomainName;
142        }
143    
144        public void setServiceUrlPath(String value) {
145            serviceUrlPath = value;
146        }
147    
148        public String getServiceUrlPath() {
149            return serviceUrlPath;
150        }
151    
152        public void setCreateConnector(Boolean flag) {
153            createConnector = flag;
154        }
155    
156        public Boolean getCreateConnector() {
157            return createConnector;
158        }
159    
160        public void setUsePlatformMBeanServer(Boolean flag) {
161            usePlatformMBeanServer = flag;
162        }
163    
164        public Boolean getUsePlatformMBeanServer() {
165            return usePlatformMBeanServer;
166        }
167    
168        public Boolean getOnlyRegisterProcessorWithCustomId() {
169            return onlyRegisterProcessorWithCustomId;
170        }
171    
172        public void setOnlyRegisterProcessorWithCustomId(Boolean onlyRegisterProcessorWithCustomId) {
173            this.onlyRegisterProcessorWithCustomId = onlyRegisterProcessorWithCustomId;
174        }
175    
176        public void setServer(MBeanServer value) {
177            server = value;
178        }
179    
180        public MBeanServer getMBeanServer() {
181            return server;
182        }
183    
184        public ExecutorService getExecutorService() {
185            return executorService;
186        }
187    
188        public void setExecutorService(ExecutorService executorService) {
189            this.executorService = executorService;
190        }
191    
192        public void register(Object obj, ObjectName name) throws JMException {
193            register(obj, name, false);
194        }
195    
196        public void register(Object obj, ObjectName name, boolean forceRegistration) throws JMException {
197            try {
198                registerMBeanWithServer(obj, name, forceRegistration);
199            } catch (NotCompliantMBeanException e) {
200                // If this is not a "normal" MBean, then try to deploy it using JMX annotations
201                ModelMBeanInfo mbi;
202                mbi = assembler.getMBeanInfo(obj, name.toString());
203                RequiredModelMBean mbean = (RequiredModelMBean)server.instantiate(RequiredModelMBean.class.getName());
204                mbean.setModelMBeanInfo(mbi);
205                try {
206                    mbean.setManagedResource(obj, "ObjectReference");
207                } catch (InvalidTargetObjectTypeException itotex) {
208                    throw new JMException(itotex.getMessage());
209                }
210                registerMBeanWithServer(mbean, name, forceRegistration);
211            }
212        }
213    
214        public void unregister(ObjectName name) throws JMException {
215            server.unregisterMBean(name);
216        }
217    
218        protected void doStart() throws Exception {
219            assembler = new MetadataMBeanInfoAssembler();
220            assembler.setAttributeSource(new AnnotationJmxAttributeSource());
221    
222            // create mbean server if is has not be injected.
223            if (server == null) {
224                finalizeSettings();
225                createMBeanServer();
226            }
227    
228            if (LOG.isDebugEnabled()) {
229                LOG.debug("Starting JMX agent on server: " + getMBeanServer());
230            }
231        }
232    
233        protected void doStop() throws Exception {
234            // close JMX Connector
235            if (cs != null) {
236                try {
237                    cs.stop();
238                } catch (IOException e) {
239                    // ignore
240                }
241                cs = null;
242            }
243    
244            // Using the array to hold the busMBeans to avoid the
245            // CurrentModificationException
246            Object[] mBeans = mbeans.toArray();
247            int caught = 0;
248            for (Object name : mBeans) {
249                mbeans.remove(name);
250                try {
251                    unregister((ObjectName)name);
252                } catch (JMException jmex) {
253                    LOG.info("Exception unregistering MBean", jmex);
254                    caught++;
255                }
256            }
257            if (caught > 0) {
258                LOG.warn("A number of " + caught
259                         + " exceptions caught while unregistering MBeans during stop operation."
260                         + " See INFO log for details.");
261            }
262        }
263    
264        private void registerMBeanWithServer(Object obj, ObjectName name, boolean forceRegistration)
265            throws JMException {
266    
267            // have we already registered the bean, there can be shared instances in the camel routes
268            boolean exists = server.isRegistered(name);
269            if (exists) {
270                if (forceRegistration) {
271                    LOG.info("ForceRegistration enabled, unregistering existing MBean");
272                    server.unregisterMBean(name);
273                } else {
274                    // okay ignore we do not want to force it and it could be a shared instance
275                    if (LOG.isDebugEnabled()) {
276                        LOG.debug("MBean already registered with objectname: " + name);
277                    }
278                }
279            }
280    
281            // register bean if by force or not exsists
282            ObjectInstance instance = null;
283            if (forceRegistration || !exists) {
284                if (LOG.isTraceEnabled()) {
285                    LOG.trace("Registering MBean with objectname: " + name);
286                }
287                instance = server.registerMBean(obj, name);
288            }
289    
290            if (instance != null) {
291                ObjectName registeredName = instance.getObjectName();
292                if (LOG.isDebugEnabled()) {
293                    LOG.debug("Registered MBean with objectname: " + registeredName);
294                }
295    
296                mbeans.add(registeredName);
297            }
298        }
299    
300        protected void createMBeanServer() {
301            String hostName;
302            boolean canAccessSystemProps = true;
303            try {
304                // we'll do it this way mostly to determine if we should lookup the
305                // hostName
306                SecurityManager sm = System.getSecurityManager();
307                if (sm != null) {
308                    sm.checkPropertiesAccess();
309                }
310            } catch (SecurityException se) {
311                canAccessSystemProps = false;
312            }
313    
314            if (canAccessSystemProps) {
315                try {
316                    hostName = InetAddress.getLocalHost().getHostName();
317                } catch (UnknownHostException uhe) {
318                    LOG.info("Cannot determine localhost name. Using default: " + DEFAULT_REGISTRY_PORT, uhe);
319                    hostName = DEFAULT_HOST;
320                }
321            } else {
322                hostName = DEFAULT_HOST;
323            }
324    
325            server = findOrCreateMBeanServer();
326    
327            try {
328                // Create the connector if we need
329                if (createConnector) {
330                    createJmxConnector(hostName);
331                }
332            } catch (IOException ioe) {
333                LOG.warn("Could not create and start JMX connector.", ioe);
334            }
335        }
336    
337        @SuppressWarnings("unchecked")
338        protected MBeanServer findOrCreateMBeanServer() {
339    
340            // return platform mbean server if the option is specified.
341            if (usePlatformMBeanServer) {
342                return ManagementFactory.getPlatformMBeanServer();
343            }
344    
345            // look for the first mbean server that has match default domain name
346            List<MBeanServer> servers = (List<MBeanServer>)MBeanServerFactory.findMBeanServer(null);
347    
348            for (MBeanServer server : servers) {
349                if (LOG.isDebugEnabled()) {
350                    LOG.debug("Found MBeanServer with default domain " + server.getDefaultDomain());
351                }
352    
353                if (mBeanServerDefaultDomain.equals(server.getDefaultDomain())) {
354                    return server;
355                }
356            }
357    
358            // create a mbean server with the given default domain name
359            return MBeanServerFactory.createMBeanServer(mBeanServerDefaultDomain);
360        }
361    
362        protected void createJmxConnector(String host) throws IOException {
363            try {
364                LocateRegistry.createRegistry(registryPort);
365                if (LOG.isDebugEnabled()) {
366                    LOG.debug("Created JMXConnector RMI regisry on port " + registryPort);
367                }
368            } catch (RemoteException ex) {
369                // The registry may had been created, we could get the registry instead
370            }
371    
372            // Create an RMI connector and start it
373            final JMXServiceURL url;
374            if (connectorPort > 0) {
375                url = new JMXServiceURL("service:jmx:rmi://" + host + ":" + connectorPort + "/jndi/rmi://" + host
376                                        + ":" + registryPort + serviceUrlPath);
377            } else {
378                url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + host + ":" + registryPort
379                                        + serviceUrlPath);
380            }
381    
382            cs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server);
383    
384            if (executorService == null) {
385                // we only need a single for the JMX connector
386                executorService = ExecutorServiceHelper.newSingleThreadExecutor("JMXConnector: " + url, true);
387            }
388    
389            // execute the JMX connector
390            executorService.execute(new Runnable() {
391                public void run() {
392                    try {
393                        cs.start();
394                    } catch (IOException ioe) {
395                        LOG.warn("Could not start JMXConnector thread.", ioe);
396                    }
397                }
398            });
399    
400            LOG.info("JMX Connector thread started and listening at: " + url);
401        }
402    
403    }