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