001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.jmx.support;
018
019import java.beans.PropertyDescriptor;
020import java.lang.management.ManagementFactory;
021import java.lang.reflect.Method;
022import java.util.Hashtable;
023import java.util.List;
024import javax.management.DynamicMBean;
025import javax.management.JMX;
026import javax.management.MBeanParameterInfo;
027import javax.management.MBeanServer;
028import javax.management.MBeanServerFactory;
029import javax.management.MalformedObjectNameException;
030import javax.management.ObjectName;
031
032import org.apache.commons.logging.Log;
033import org.apache.commons.logging.LogFactory;
034
035import org.springframework.jmx.MBeanServerNotFoundException;
036import org.springframework.util.ClassUtils;
037import org.springframework.util.CollectionUtils;
038import org.springframework.util.ObjectUtils;
039import org.springframework.util.StringUtils;
040
041/**
042 * Collection of generic utility methods to support Spring JMX.
043 * Includes a convenient method to locate an MBeanServer.
044 *
045 * @author Rob Harrop
046 * @author Juergen Hoeller
047 * @since 1.2
048 * @see #locateMBeanServer
049 */
050public abstract class JmxUtils {
051
052        /**
053         * The key used when extending an existing {@link ObjectName} with the
054         * identity hash code of its corresponding managed resource.
055         */
056        public static final String IDENTITY_OBJECT_NAME_KEY = "identity";
057
058        /**
059         * Suffix used to identify an MBean interface.
060         */
061        private static final String MBEAN_SUFFIX = "MBean";
062
063
064        private static final Log logger = LogFactory.getLog(JmxUtils.class);
065
066
067        /**
068         * Attempt to find a locally running {@code MBeanServer}. Fails if no
069         * {@code MBeanServer} can be found. Logs a warning if more than one
070         * {@code MBeanServer} found, returning the first one from the list.
071         * @return the {@code MBeanServer} if found
072         * @throws MBeanServerNotFoundException if no {@code MBeanServer} could be found
073         * @see javax.management.MBeanServerFactory#findMBeanServer
074         */
075        public static MBeanServer locateMBeanServer() throws MBeanServerNotFoundException {
076                return locateMBeanServer(null);
077        }
078
079        /**
080         * Attempt to find a locally running {@code MBeanServer}. Fails if no
081         * {@code MBeanServer} can be found. Logs a warning if more than one
082         * {@code MBeanServer} found, returning the first one from the list.
083         * @param agentId the agent identifier of the MBeanServer to retrieve.
084         * If this parameter is {@code null}, all registered MBeanServers are considered.
085         * If the empty String is given, the platform MBeanServer will be returned.
086         * @return the {@code MBeanServer} if found
087         * @throws MBeanServerNotFoundException if no {@code MBeanServer} could be found
088         * @see javax.management.MBeanServerFactory#findMBeanServer(String)
089         */
090        public static MBeanServer locateMBeanServer(String agentId) throws MBeanServerNotFoundException {
091                MBeanServer server = null;
092
093                // null means any registered server, but "" specifically means the platform server
094                if (!"".equals(agentId)) {
095                        List<MBeanServer> servers = MBeanServerFactory.findMBeanServer(agentId);
096                        if (!CollectionUtils.isEmpty(servers)) {
097                                // Check to see if an MBeanServer is registered.
098                                if (servers.size() > 1 && logger.isWarnEnabled()) {
099                                        logger.warn("Found more than one MBeanServer instance" +
100                                                        (agentId != null ? " with agent id [" + agentId + "]" : "") +
101                                                        ". Returning first from list.");
102                                }
103                                server = servers.get(0);
104                        }
105                }
106
107                if (server == null && !StringUtils.hasLength(agentId)) {
108                        // Attempt to load the PlatformMBeanServer.
109                        try {
110                                server = ManagementFactory.getPlatformMBeanServer();
111                        }
112                        catch (SecurityException ex) {
113                                throw new MBeanServerNotFoundException("No specific MBeanServer found, " +
114                                                "and not allowed to obtain the Java platform MBeanServer", ex);
115                        }
116                }
117
118                if (server == null) {
119                        throw new MBeanServerNotFoundException(
120                                        "Unable to locate an MBeanServer instance" +
121                                        (agentId != null ? " with agent id [" + agentId + "]" : ""));
122                }
123
124                if (logger.isDebugEnabled()) {
125                        logger.debug("Found MBeanServer: " + server);
126                }
127                return server;
128        }
129
130        /**
131         * Convert an array of {@code MBeanParameterInfo} into an array of
132         * {@code Class} instances corresponding to the parameters.
133         * @param paramInfo the JMX parameter info
134         * @return the parameter types as classes
135         * @throws ClassNotFoundException if a parameter type could not be resolved
136         */
137        public static Class<?>[] parameterInfoToTypes(MBeanParameterInfo[] paramInfo) throws ClassNotFoundException {
138                return parameterInfoToTypes(paramInfo, ClassUtils.getDefaultClassLoader());
139        }
140
141        /**
142         * Convert an array of {@code MBeanParameterInfo} into an array of
143         * {@code Class} instances corresponding to the parameters.
144         * @param paramInfo the JMX parameter info
145         * @param classLoader the ClassLoader to use for loading parameter types
146         * @return the parameter types as classes
147         * @throws ClassNotFoundException if a parameter type could not be resolved
148         */
149        public static Class<?>[] parameterInfoToTypes(MBeanParameterInfo[] paramInfo, ClassLoader classLoader)
150                        throws ClassNotFoundException {
151
152                Class<?>[] types = null;
153                if (paramInfo != null && paramInfo.length > 0) {
154                        types = new Class<?>[paramInfo.length];
155                        for (int x = 0; x < paramInfo.length; x++) {
156                                types[x] = ClassUtils.forName(paramInfo[x].getType(), classLoader);
157                        }
158                }
159                return types;
160        }
161
162        /**
163         * Create a {@code String[]} representing the argument signature of a
164         * method. Each element in the array is the fully qualified class name
165         * of the corresponding argument in the methods signature.
166         * @param method the method to build an argument signature for
167         * @return the signature as array of argument types
168         */
169        public static String[] getMethodSignature(Method method) {
170                Class<?>[] types = method.getParameterTypes();
171                String[] signature = new String[types.length];
172                for (int x = 0; x < types.length; x++) {
173                        signature[x] = types[x].getName();
174                }
175                return signature;
176        }
177
178        /**
179         * Return the JMX attribute name to use for the given JavaBeans property.
180         * <p>When using strict casing, a JavaBean property with a getter method
181         * such as {@code getFoo()} translates to an attribute called
182         * {@code Foo}. With strict casing disabled, {@code getFoo()}
183         * would translate to just {@code foo}.
184         * @param property the JavaBeans property descriptor
185         * @param useStrictCasing whether to use strict casing
186         * @return the JMX attribute name to use
187         */
188        public static String getAttributeName(PropertyDescriptor property, boolean useStrictCasing) {
189                if (useStrictCasing) {
190                        return StringUtils.capitalize(property.getName());
191                }
192                else {
193                        return property.getName();
194                }
195        }
196
197        /**
198         * Append an additional key/value pair to an existing {@link ObjectName} with the key being
199         * the static value {@code identity} and the value being the identity hash code of the
200         * managed resource being exposed on the supplied {@link ObjectName}. This can be used to
201         * provide a unique {@link ObjectName} for each distinct instance of a particular bean or
202         * class. Useful when generating {@link ObjectName ObjectNames} at runtime for a set of
203         * managed resources based on the template value supplied by a
204         * {@link org.springframework.jmx.export.naming.ObjectNamingStrategy}.
205         * @param objectName the original JMX ObjectName
206         * @param managedResource the MBean instance
207         * @return an ObjectName with the MBean identity added
208         * @throws MalformedObjectNameException in case of an invalid object name specification
209         * @see org.springframework.util.ObjectUtils#getIdentityHexString(Object)
210         */
211        public static ObjectName appendIdentityToObjectName(ObjectName objectName, Object managedResource)
212                        throws MalformedObjectNameException {
213
214                Hashtable<String, String> keyProperties = objectName.getKeyPropertyList();
215                keyProperties.put(IDENTITY_OBJECT_NAME_KEY, ObjectUtils.getIdentityHexString(managedResource));
216                return ObjectNameManager.getInstance(objectName.getDomain(), keyProperties);
217        }
218
219        /**
220         * Return the class or interface to expose for the given bean.
221         * This is the class that will be searched for attributes and operations
222         * (for example, checked for annotations).
223         * <p>This implementation returns the superclass for a CGLIB proxy and
224         * the class of the given bean else (for a JDK proxy or a plain bean class).
225         * @param managedBean the bean instance (might be an AOP proxy)
226         * @return the bean class to expose
227         * @see org.springframework.util.ClassUtils#getUserClass(Object)
228         */
229        public static Class<?> getClassToExpose(Object managedBean) {
230                return ClassUtils.getUserClass(managedBean);
231        }
232
233        /**
234         * Return the class or interface to expose for the given bean class.
235         * This is the class that will be searched for attributes and operations
236         * (for example, checked for annotations).
237         * <p>This implementation returns the superclass for a CGLIB proxy and
238         * the class of the given bean else (for a JDK proxy or a plain bean class).
239         * @param clazz the bean class (might be an AOP proxy class)
240         * @return the bean class to expose
241         * @see org.springframework.util.ClassUtils#getUserClass(Class)
242         */
243        public static Class<?> getClassToExpose(Class<?> clazz) {
244                return ClassUtils.getUserClass(clazz);
245        }
246
247        /**
248         * Determine whether the given bean class qualifies as an MBean as-is.
249         * <p>This implementation checks for {@link javax.management.DynamicMBean}
250         * classes as well as classes with corresponding "*MBean" interface
251         * (Standard MBeans) or corresponding "*MXBean" interface (Java 6 MXBeans).
252         * @param clazz the bean class to analyze
253         * @return whether the class qualifies as an MBean
254         * @see org.springframework.jmx.export.MBeanExporter#isMBean(Class)
255         */
256        public static boolean isMBean(Class<?> clazz) {
257                return (clazz != null &&
258                                (DynamicMBean.class.isAssignableFrom(clazz) ||
259                                                (getMBeanInterface(clazz) != null || getMXBeanInterface(clazz) != null)));
260        }
261
262        /**
263         * Return the Standard MBean interface for the given class, if any
264         * (that is, an interface whose name matches the class name of the
265         * given class but with suffix "MBean").
266         * @param clazz the class to check
267         * @return the Standard MBean interface for the given class
268         */
269        public static Class<?> getMBeanInterface(Class<?> clazz) {
270                if (clazz == null || clazz.getSuperclass() == null) {
271                        return null;
272                }
273                String mbeanInterfaceName = clazz.getName() + MBEAN_SUFFIX;
274                Class<?>[] implementedInterfaces = clazz.getInterfaces();
275                for (Class<?> iface : implementedInterfaces) {
276                        if (iface.getName().equals(mbeanInterfaceName)) {
277                                return iface;
278                        }
279                }
280                return getMBeanInterface(clazz.getSuperclass());
281        }
282
283        /**
284         * Return the Java 6 MXBean interface exists for the given class, if any
285         * (that is, an interface whose name ends with "MXBean" and/or
286         * carries an appropriate MXBean annotation).
287         * @param clazz the class to check
288         * @return whether there is an MXBean interface for the given class
289         */
290        public static Class<?> getMXBeanInterface(Class<?> clazz) {
291                if (clazz == null || clazz.getSuperclass() == null) {
292                        return null;
293                }
294                Class<?>[] implementedInterfaces = clazz.getInterfaces();
295                for (Class<?> iface : implementedInterfaces) {
296                        if (JMX.isMXBeanInterface(iface)) {
297                                return iface;
298                        }
299                }
300                return getMXBeanInterface(clazz.getSuperclass());
301        }
302
303        /**
304         * Check whether MXBean support is available, i.e. whether we're running
305         * on Java 6 or above.
306         * @return {@code true} if available; {@code false} otherwise
307         * @deprecated as of Spring 4.0, since Java 6 is required anyway now
308         */
309        @Deprecated
310        public static boolean isMXBeanSupportAvailable() {
311                return true;
312        }
313
314}