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}