/*
* @(#)StandardMetaDataImpl.java 1.24 05/05/27
*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package com.sun.jmx.mbeanserver;
// java import
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.ref.WeakReference;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Hashtable;
import java.util.Iterator;
import java.io.PrintWriter;
import java.io.StringWriter;
// RI import
import javax.management.* ;
import com.sun.jmx.trace.Trace;
import com.sun.jmx.mbeanserver.GetPropertyAction;
/**
* The MetaData class provides local access to the metadata service in
* an agent.
*
* @since 1.5
* @since.unbundled JMX RI 1.2
*/
class StandardMetaDataImpl extends BaseMetaDataImpl {
/** The name of this class to be used for tracing */
private final static String dbgTag = "StandardMetaDataImpl";
/**
* Cache of MBeanInfo objects.
*/
private static java.util.Map mbeanInfoCache =
new java.util.WeakHashMap();
/**
* Cache of MBean Interface objects.
*/
private static java.util.Map mbeanInterfaceCache =
new java.util.WeakHashMap();
/**
* True if RuntimeExceptions from getters, setters, and operations
* should be wrapped in RuntimeMBeanException. We do not have
* similar logic for Errors because DynamicMetaDataImpl does not
* re-wrap RuntimeErrorException as it would
* RuntimeMBeanException.
*/
private final boolean wrapRuntimeExceptions;
/**
* objects maps from primitive classes to primitive object classes.
*/
// private static Hashtable primitiveobjects = new Hashtable();
// {
// primitiveobjects.put(Boolean.TYPE, getClass("java.lang.Boolean"));
// primitiveobjects.put(Character.TYPE, getClass("java.lang.Character"));
// primitiveobjects.put(Byte.TYPE, getClass("java.lang.Byte"));
// primitiveobjects.put(Short.TYPE, getClass("java.lang.Short"));
// primitiveobjects.put(Integer.TYPE, getClass("java.lang.Integer"));
// primitiveobjects.put(Long.TYPE, getClass("java.lang.Long"));
// primitiveobjects.put(Float.TYPE, getClass("java.lang.Float"));
// primitiveobjects.put(Double.TYPE, getClass("java.lang.Double"));
// }
private final static Hashtable primitiveClasses = new Hashtable(8);
{
primitiveClasses.put(Boolean.TYPE.toString(), Boolean.TYPE);
primitiveClasses.put(Character.TYPE.toString(), Character.TYPE);
primitiveClasses.put(Byte.TYPE.toString(), Byte.TYPE);
primitiveClasses.put(Short.TYPE.toString(), Short.TYPE);
primitiveClasses.put(Integer.TYPE.toString(), Integer.TYPE);
primitiveClasses.put(Long.TYPE.toString(), Long.TYPE);
primitiveClasses.put(Float.TYPE.toString(), Float.TYPE);
primitiveClasses.put(Double.TYPE.toString(), Double.TYPE);
}
/**
* Creates a Metadata Service.
*/
public StandardMetaDataImpl() {
this(true);
}
StandardMetaDataImpl(boolean wrapRuntimeExceptions) {
this.wrapRuntimeExceptions = wrapRuntimeExceptions;
}
/**
* Builds the MBeanInfo from the given concrete MBean class.
* @param c The concrete MBean class from which the MBeanInfo
* must be built.
*
* @exception NotCompliantMBeanException if the given class
* is not MBean compliant.
* @return the MBeanInfo built from class c, or null
* if class c implements
* {@link javax.management.DynamicMBean}
*/
public synchronized MBeanInfo buildMBeanInfo(Class c)
throws NotCompliantMBeanException {
return Introspector.testCompliance(c);
}
/**
* Builds the MBeanInfo from the given concrete MBean class,
* using the given mbeanInterface as Management Interface.
*
* @param c The concrete MBean class from which the MBeanInfo
* must be built.
* @param mbeanInterface The management interface of the MBean.
* If null
, will use the regular design pattern
* to determine the management interface.
* @exception NotCompliantMBeanException if the given class and interface
* are not MBean compliant. Does not enforce that if class c
* is "X", then interface mbeanInterface is "XMBean".
* @return the MBeanInfo built from class c, according
* to interface mbeanInterface. Does not check whether
* class c implements {@link javax.management.DynamicMBean}.
**/
public synchronized
MBeanInfo buildMBeanInfo(Class c, Class mbeanInterface)
throws NotCompliantMBeanException {
return Introspector.testCompliance(c,mbeanInterface);
}
/**
* This methods tests if the MBean is JMX compliant
*/
public synchronized void testCompliance(Class c)
throws NotCompliantMBeanException {
// ------------------------------
// ------------------------------
final MBeanInfo mbeanInfo = buildMBeanInfo(c);
final Class mbeanInterface = Introspector.getMBeanInterface(c);
cacheMBeanInfo(c,mbeanInterface,mbeanInfo);
}
/**
* This methods tests if the MBean is JMX compliant.
*
It does so by calling
* getMBeanInterfaceFromClass(instance.getClass());
* @param instance the MBean instance.
*/
Class getMBeanInterfaceFromInstance(Object instance) {
if (instance == null) return null;
return getMBeanInterfaceFromClass(instance.getClass());
}
/**
* Cache the MBeanInfo and MBean interface obtained for class
* c.
*
This method is called by testCompliance(...)
* after compliance is successfully verified. It uses two
* {@link java.util.WeakHashMap WeakHashMaps} - one for the
* MBeanInfo, one for the MBeanInterface, with calss c
* as the key.
*
* @param c The concrete MBean class from which the MBeanInfo
* was be built.
*
* @param mbeanInterface The management interface of the MBean.
* Note that caching will not work if two MBeans of the same
* class can have different mbeanInterface's. If you want
* to use caching nonetheless, you will have to
* to do it by redefining the method
* {@link #getMBeanInterfaceFromInstance(java.lang.Object)
* getMBeanInterfaceFromInstance()}.
* @param info The MBeanInfo obtained from class c using
* interface mbeanInterface.
*
**/
void cacheMBeanInfo(Class c, Class mbeanInterface,
MBeanInfo info)
throws NotCompliantMBeanException {
if (info != null) {
synchronized (mbeanInfoCache) {
if (mbeanInfoCache.get(c) == null) {
mbeanInfoCache.put(c, info);
}
}
}
if (mbeanInterface != null) {
synchronized (mbeanInterfaceCache) {
if ((mbeanInterfaceCache.get(c) == null) || (((WeakReference)mbeanInterfaceCache.get(c)).get() == null)) {
mbeanInterfaceCache.put(c, new WeakReference(mbeanInterface));
}
}
}
}
/**
* Returns the MBean interface that was cached for class c.
* @param c The concrete MBean class.
* @return The cached MBean interface if found, null otherwise.
**/
Class getCachedMBeanInterface(Class c) {
synchronized (mbeanInterfaceCache) {
return (Class)(((WeakReference)mbeanInterfaceCache.get(c)).get());
}
}
/**
* Returns the MBeanInfo that was cached for class c.
* @param c The concrete MBean class.
* @return The cached MBeanInfo if found, null otherwise.
**/
MBeanInfo getCachedMBeanInfo(Class c) {
synchronized (mbeanInfoCache) {
return (MBeanInfo)mbeanInfoCache.get(c);
}
}
/**
* Find a class using the specified ClassLoader.
**/
Class findClass(String className, ClassLoader loader)
throws ReflectionException {
return MBeanInstantiatorImpl.loadClass(className,
loader);
}
/**
* Find the classes from a signature using the specified ClassLoader.
**/
Class[] findSignatureClasses(String[] signature,
ClassLoader loader)
throws ReflectionException {
return ((signature == null)?null:
MBeanInstantiatorImpl.loadSignatureClasses(signature,loader));
}
/**
* Invoke getAttribute through reflection on a standard MBean instance.
**/
Object getAttribute(Object instance, String attribute,
Class mbeanClass)
throws MBeanException, AttributeNotFoundException,
ReflectionException {
if (attribute == null) {
final RuntimeException r =
new IllegalArgumentException("Attribute name cannot be null");
throw new RuntimeOperationsException(r,
"Exception occured trying to invoke the getter on the MBean");
}
// Standard MBean: need to reflect...
Method meth = null;
meth = findGetter(mbeanClass, attribute);
if (meth == null) {
if (isTraceOn()) {
trace("getAttribute", "Cannot find getter for "+attribute+
" in class " + mbeanClass.getName());
}
throw new AttributeNotFoundException(attribute +
" not accessible");
}
// Invoke the getter
if (isTraceOn()) {
trace("getAttribute", "Invoke callback");
}
Object result= null;
try {
result = meth.invoke(instance, (Object[]) null);
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t instanceof RuntimeException) {
debugX("getAttribute",t);
final String msg =
"RuntimeException thrown in the getter for the attribute "
+ attribute;
throw wrapRuntimeException((RuntimeException) t, msg);
} else if (t instanceof Error) {
debugX("getAttribute",t);
throw new RuntimeErrorException((Error) t ,
"Error thrown in the getter for the attribute " +
attribute);
} else {
debugX("getAttribute",t);
throw new MBeanException((Exception) t,
"Exception thrown in the getter for the attribute " +
attribute);
}
} catch (RuntimeException e) {
debugX("getAttribute",e);
throw new RuntimeOperationsException(e,
"RuntimeException thrown trying to invoke the getter" +
" for the attribute " + attribute);
} catch (IllegalAccessException e) {
debugX("getAttribute",e);
throw new ReflectionException(e, "Exception thrown trying to" +
" invoke the getter for the attribute " + attribute);
} catch (Error e) {
debugX("getAttribute",e);
throw new RuntimeErrorException((Error)e,
"Error thrown trying to invoke the getter " +
" for the attribute " + attribute);
}
if (isTraceOn()) {
trace("getAttribute", attribute + "= " + result + "\n");
}
return result;
}
/**
* Invoke setAttribute through reflection on a standard MBean instance.
**/
Object setAttribute(Object instance, Attribute attribute,
Class mbeanClass)
throws AttributeNotFoundException, InvalidAttributeValueException,
MBeanException, ReflectionException {
if (attribute == null) {
final RuntimeException r =
new IllegalArgumentException("Attribute name cannot be null");
throw new RuntimeOperationsException(r,
"Exception occured trying to invoke the setter on the MBean");
}
final Class objClass = instance.getClass();
final ClassLoader aLoader = objClass.getClassLoader();
Object result = null;
final Object value = attribute.getValue();
final String attname = attribute.getName();
// Query the metadata service to get the appropriate setter
// of the object.
Method meth = null;
if (value == null) {
meth = findSetter(mbeanClass, attname);
} else {
meth = findSetter(mbeanClass, attname, value.getClass());
}
if (meth == null) {
// Check whether the type is a primitive one
Class primClass = findPrimForClass(value);
if (primClass != null) {
meth = findSetter(mbeanClass, attname, primClass);
}
}
if (meth == null) {
// Try to check if the attribute name does correspond to a
// valid property
meth= findSetter(mbeanClass, attname);
if (meth == null) {
if (isTraceOn()) {
trace("setAttribute", "Cannot find setter for "+attribute+
" in class " + mbeanClass.getName());
}
throw new AttributeNotFoundException( attname +
" not accessible");
} else {
final Object v = attribute.getValue();
if (v == null) {
throw new InvalidAttributeValueException("attribute= " +
attname + " value = null");
} else {
throw new InvalidAttributeValueException("attribute= " +
attname + " value = " + v);
}
}
}
// Invoke the setter
if (isTraceOn()) {
trace("setAttribute", "Invoking the set method for " +
attname);
}
final Object[] values = new Object[1];
values[0] = value;
try {
result = meth.invoke(instance,values);
} catch (IllegalAccessException e) {
debugX("setAttribute",e);
// Wrap the exception.
throw new ReflectionException(e, "IllegalAccessException" +
" occured trying to invoke the setter on the MBean");
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
debugX("setAttribute",t);
if (t instanceof RuntimeException) {
final String msg =
"RuntimeException thrown in the setter for the attribute "
+ attribute;
throw wrapRuntimeException((RuntimeException) t, msg);
} else if (t instanceof Error) {
throw new RuntimeErrorException((Error) t,
"Error thrown in the MBean's setter");
} else {
throw new MBeanException((Exception) t,
"Exception thrown in the MBean's setter");
}
}
if (isTraceOn()) {
trace("setAttribute", attname + "= " + value);
}
return value;
}
/**
* Returns the MBeanNotificationInfo of the MBeans that implement
* the NotificationBroadcaster interface.
*/
MBeanNotificationInfo[] findNotifications(Object moi) {
if (moi instanceof javax.management.NotificationBroadcaster) {
MBeanNotificationInfo[] mbn =
((NotificationBroadcaster)moi).getNotificationInfo();
if (mbn == null) {
return new MBeanNotificationInfo[0];
}
MBeanNotificationInfo[] result =
new MBeanNotificationInfo[mbn.length];
for (int i = 0; i < mbn.length; i++) {
result[i] = (MBeanNotificationInfo) mbn[i].clone();
}
return result;
}
return new MBeanNotificationInfo[0];
}
/**
* Finds a specific method of an object.
* Returns the method or null if not found
*/
public static Method findMethod(Class classObj, String name,
Class parameterTypes[]) {
Method method=null;
try {
method= classObj.getMethod(name, parameterTypes);
} catch(Exception e) {
// OK: will return null.
}
return method;
}
/**
* Finds a specific method of an object without knowing the parameter
* types.
* Returns the method or null if not found
*/
public static Method findMethod(Class classObj, String name) {
Method method = null ;
try {
Method[] methods=classObj.getMethods();
int i = 0;
while ((i < methods.length) &&
!methods[i].getName().equals(name)) {
i++;
}
if (i < methods.length) {
method = methods[i];
}
} catch(Exception e) {
// OK: will return null.
}
return method;
}
/**
* Finds a specific method of an object given the number of parameters.
* Returns the method or null if not found
*/
public static Method findMethod(Class classObj, String name,
int paramCount) {
Method method = null;
try {
Method[] methods=classObj.getMethods();
int i = 0;
boolean found = false;
while ((i < methods.length) && !found) {
found = methods[i].getName().equals(name);
if (found) { // Now check if the number of parameters
found = (methods[i].getParameterTypes().length ==
paramCount);
}
i++;
}
if (found) {
method = methods[i-1] ; // Note i-1 !
}
} catch(Exception e) {
// OK: will return null;
}
return method;
}
/**
* Finds the getter of a specific attribute in an object.
* Returns the method for accessing the attributes, null otherwise
*/
public static Method findGetter(Class classObj, String attribute) {
// Methods called "is" or "get" tout court are not getters
if (attribute.length() == 0)
return null;
// Look for a method T getX(), where T is not void
Method m = findMethod(classObj, "get" + attribute, null);
if (m != null && m.getReturnType() != void.class)
return m;
// Look for a method boolean isX()
// must not be any other type than "boolean", including not "Boolean"
m = findMethod(classObj, "is" + attribute, null);
if (m != null && m.getReturnType() == boolean.class)
return m;
return null;
}
/**
* Finds the setter of a specific attribute in an object.
* Returns the method for accessing the attribute, null otherwise
*/
public static Method findSetter(Class classObj, String attribute,
Class type) {
Method mth= findMethod(classObj, "set" + attribute, 1);
if (mth != null) {
Class[] pars = mth.getParameterTypes();
if (pars[0].isAssignableFrom(type)) {
return mth;
}
}
return null;
}
/**
* Finds the setter of a specific attribute without knowing its type.
* Returns the method for accessing the attribute, null otherwise
*/
public static Method findSetter(Class classObj, String attribute) {
return findMethod(classObj, "set" + attribute, 1) ;
}
/**
* Finds a specific constructor of a class
* Returns the requested constructor or null if not found
*/
public static Constructor findConstructor(Class theClass,
Class parameterTypes[]) {
// Get the list of methods
Constructor mth = null;
try {
mth = theClass.getConstructor(parameterTypes);
} catch(Exception e) {
return null;
}
return mth;
}
/**
* Get the class of the constructed type
* corresponding to the given primitive type
*/
public static Class findClassForPrim(String primName) {
return (Class) primitiveClasses.get(primName);
}
/**
* Get the class of the primitive type
* corresponding to the given constructed object.
*/
public static Class findPrimForClass(Object value) {
if (value instanceof Boolean)
return Boolean.TYPE;
else if (value instanceof Character)
return Character.TYPE;
else if (value instanceof Byte)
return Byte.TYPE;
else if (value instanceof Short)
return Short.TYPE;
else if (value instanceof Integer)
return Integer.TYPE;
else if (value instanceof Long)
return Long.TYPE;
else if (value instanceof Float)
return Float.TYPE;
else if (value instanceof Double)
return Double.TYPE;
return null;
}
/**
* Converts the array of classes to an array of class signatures.
*/
static String[] findSignatures(Class[] clz) {
String signers[] = new String[clz.length];
for (int i = 0; i < clz.length; i++) {
signers[i] = findSignature(clz[i]);
}
return signers;
}
/**
* Converts the class to a class signature.
*/
static String findSignature(Class clz) {
return clz.getName();
}
private RuntimeException wrapRuntimeException(RuntimeException re,
String msg) {
if (wrapRuntimeExceptions)
return new RuntimeMBeanException(re, msg);
else
return re;
}
// TRACES & DEBUG
//---------------
private static boolean isTraceOn() {
return Trace.isSelected(Trace.LEVEL_TRACE, Trace.INFO_MBEANSERVER);
}
private static void trace(String clz, String func, String info) {
Trace.send(Trace.LEVEL_TRACE, Trace.INFO_MBEANSERVER, clz, func, info);
}
private static void trace(String func, String info) {
trace(dbgTag, func, info);
}
private static boolean isDebugOn() {
return Trace.isSelected(Trace.LEVEL_DEBUG, Trace.INFO_MBEANSERVER);
}
private static void debug(String clz, String func, String info) {
Trace.send(Trace.LEVEL_DEBUG, Trace.INFO_MBEANSERVER, clz, func, info);
}
private static void debug(String func, String info) {
debug(dbgTag, func, info);
}
private static void debugX(String func,Throwable e) {
if (isDebugOn()) {
final StringWriter s = new StringWriter();
e.printStackTrace(new PrintWriter(s));
final String stack = s.toString();
debug(dbgTag,func,"Exception caught in "+ func+"(): "+e);
debug(dbgTag,func,stack);
// java.lang.System.err.println("**** Exception caught in "+
// func+"(): "+e);
// java.lang.System.err.println(stack);
}
}
}