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