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