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}