org.infinispan.jmx.ResourceDMBean Maven / Gradle / Ivy
package org.infinispan.jmx;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.ReflectionException;
import javax.management.ServiceNotFoundException;
import org.infinispan.commons.util.CollectionFactory;
import org.infinispan.commons.util.ReflectionUtil;
import org.infinispan.factories.components.JmxAttributeMetadata;
import org.infinispan.factories.components.JmxOperationMetadata;
import org.infinispan.factories.components.JmxOperationParameter;
import org.infinispan.factories.components.ManageableComponentMetadata;
import org.infinispan.jmx.annotations.MBean;
import org.infinispan.jmx.annotations.ManagedOperation;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
/**
* This class was entirely copied from JGroups 2.7 (same name there). Couldn't simply reuse it because JGroups does not
* ship with MBean, ManagedAttribute and ManagedOperation.
*
* The original JGroup's ResourceDMBean logic has been modified so that invoke() method checks whether the operation
* called has been exposed as a {@link ManagedOperation}, otherwise the call fails. JGroups deviated from this logic on
* purpose because they liked the fact that you could expose all class methods by simply annotating class with {@link
* MBean} annotation.
*
* @author [email protected]
* @author Galder Zamarreño
* @since 4.0
*/
public class ResourceDMBean implements DynamicMBean {
private static final String MBEAN_DESCRITION = "Dynamic MBean Description";
private static final Log log = LogFactory.getLog(ResourceDMBean.class);
private static final boolean trace = log.isTraceEnabled();
private final Object obj;
private final Class> objectClass;
private final IspnMBeanOperationInfo[] opInfos;
private final MBeanAttributeInfo[] attInfos;
private final HashMap atts = new HashMap(2);
private final ManageableComponentMetadata mBeanMetadata;
private static final Map FIELD_CACHE = CollectionFactory.makeConcurrentMap(64);
private static final Map METHOD_CACHE = CollectionFactory.makeConcurrentMap(64);
public ResourceDMBean(Object instance, ManageableComponentMetadata mBeanMetadata) throws NoSuchFieldException, ClassNotFoundException {
if (instance == null)
throw new NullPointerException("Cannot make an MBean wrapper for null instance");
this.obj = instance;
this.objectClass = instance.getClass();
this.mBeanMetadata = mBeanMetadata;
// Load up all fields.
int i = 0;
attInfos = new MBeanAttributeInfo[mBeanMetadata.getAttributeMetadata().size()];
for (JmxAttributeMetadata attributeMetadata : mBeanMetadata.getAttributeMetadata()) {
String attributeName = attributeMetadata.getName();
InvokableMBeanAttributeInfo info = toJmxInfo(attributeMetadata);
if (atts.containsKey(attributeName)) {
throw new IllegalArgumentException("Component " + mBeanMetadata.getName()
+ " metadata has a duplicate attribute: " + attributeName);
}
atts.put(attributeName, info);
attInfos[i++] = info.getMBeanAttributeInfo();
if (trace)
log.tracef("Attribute %s [r=%b,w=%b,is=%b,type=%s]", attributeName,
info.getMBeanAttributeInfo().isReadable(), info.getMBeanAttributeInfo().isWritable(),
info.getMBeanAttributeInfo().isIs(), info.getMBeanAttributeInfo().getType());
}
// And operations
IspnMBeanOperationInfo op;
opInfos = new IspnMBeanOperationInfo[mBeanMetadata.getOperationMetadata().size()];
i = 0;
for (JmxOperationMetadata operation : mBeanMetadata.getOperationMetadata()) {
op = toJmxInfo(operation);
opInfos[i++] = op;
if (trace) log.tracef("Operation %s %s", op.getReturnType(), op.getName());
}
}
private static Field findField(Class> objectClass, String fieldName) throws NoSuchFieldException {
String key = objectClass.getName() + "#" + fieldName;
Field f = FIELD_CACHE.get(key);
if (f == null) {
f = ReflectionUtil.getField(fieldName, objectClass);
if (f != null) FIELD_CACHE.put(key, f);
}
return f;
}
private static Method findSetter(Class> objectClass, String fieldName) throws NoSuchFieldException {
String key = objectClass.getName() + "#s#" + fieldName;
Method m = METHOD_CACHE.get(key);
if (m == null) {
m = ReflectionUtil.findSetterForField(objectClass, fieldName);
if (m != null) METHOD_CACHE.put(key, m);
}
return m;
}
private static Method findGetter(Class> objectClass, String fieldName) throws NoSuchFieldException {
String key = objectClass.getName() + "#g#" + fieldName;
Method m = METHOD_CACHE.get(key);
if (m == null) {
m = ReflectionUtil.findGetterForField(objectClass, fieldName);
if (m != null) METHOD_CACHE.put(key, m);
}
return m;
}
private InvokableMBeanAttributeInfo toJmxInfo(JmxAttributeMetadata attributeMetadata) throws NoSuchFieldException {
if (!attributeMetadata.isUseSetter()) {
Field field = findField(objectClass, attributeMetadata.getName());
if (field != null) {
return new InvokableFieldBasedMBeanAttributeInfo(attributeMetadata.getName(), attributeMetadata.getType(),
attributeMetadata.getDescription(), true, attributeMetadata.isWritable(),
attributeMetadata.isIs(), field, this);
}
}
Method setter = attributeMetadata.isWritable() ? findSetter(objectClass, attributeMetadata.getName()) : null;
Method getter = findGetter(objectClass, attributeMetadata.getName());
return new InvokableSetterBasedMBeanAttributeInfo(attributeMetadata.getName(), attributeMetadata.getType(),
attributeMetadata.getDescription(), true, attributeMetadata.isWritable(),
attributeMetadata.isIs(), getter, setter, this);
}
private IspnMBeanOperationInfo toJmxInfo(JmxOperationMetadata operationMetadata) throws ClassNotFoundException {
JmxOperationParameter[] parameters = operationMetadata.getMethodParameters();
MBeanParameterInfo[] params = new MBeanParameterInfo[parameters.length];
for (int i = 0; i < parameters.length; i++) {
params[i] = new MBeanParameterInfo(parameters[i].getName(), parameters[i].getType(), parameters[i].getDescription());
}
return new IspnMBeanOperationInfo(operationMetadata.getMethodName(), operationMetadata.getDescription(), params, operationMetadata.getReturnType(), MBeanOperationInfo.UNKNOWN,
operationMetadata.getOperationName());
}
Object getObject() {
return obj;
}
@Override
public synchronized MBeanInfo getMBeanInfo() {
//the client doesn't know about IspnMBeanOperationInfo so we need to convert first
MBeanOperationInfo[] operationInfoForClient = new MBeanOperationInfo[opInfos.length];
for (int i = 0; i < opInfos.length; i++) {
IspnMBeanOperationInfo current = opInfos[i];
operationInfoForClient[i] = new MBeanOperationInfo(current.getOperationName(), current.getDescription(),
current.getSignature(), current.getReturnType(), MBeanOperationInfo.UNKNOWN);
}
return new MBeanInfo(getObject().getClass().getCanonicalName(), mBeanMetadata.getDescription(), attInfos, null, operationInfoForClient, null);
}
@Override
public Object getAttribute(String name) throws AttributeNotFoundException {
if (name == null || name.length() == 0)
throw new NullPointerException("Invalid attribute requested " + name);
Attribute attr = getNamedAttribute(name);
if (attr == null) {
throw new AttributeNotFoundException("Unknown attribute '" + name
+ "'. Known attributes names are: " + atts.keySet());
}
return attr.getValue();
}
@Override
public synchronized void setAttribute(Attribute attribute) throws AttributeNotFoundException, MBeanException {
if (attribute == null || attribute.getName() == null)
throw new NullPointerException("Invalid attribute requested " + attribute);
setNamedAttribute(attribute);
}
@Override
public synchronized AttributeList getAttributes(String[] names) {
AttributeList al = new AttributeList();
for (String name : names) {
Attribute attr = getNamedAttribute(name);
if (attr != null) {
al.add(attr);
} else {
log.couldNotFindAttribute(name);
}
}
return al;
}
@Override
public synchronized AttributeList setAttributes(AttributeList list) {
AttributeList results = new AttributeList();
for (Object aList : list) {
Attribute attr = (Attribute) aList;
try {
setNamedAttribute(attr);
results.add(attr);
} catch (Exception e) {
log.failedToUpdateAttribute(attr.getName(), attr.getValue());
}
}
return results;
}
@Override
public Object invoke(String name, Object[] args, String[] sig) throws MBeanException,
ReflectionException {
if (log.isDebugEnabled()) {
log.debugf("Invoke method called on %s", name);
}
MBeanOperationInfo opInfo = null;
for (IspnMBeanOperationInfo op : opInfos) {
if (op.getOperationName().equals(name)) {
opInfo = op;
break;
}
}
if (opInfo == null) {
final String msg = "Operation " + name + " not amongst operations in " + opInfos;
throw new MBeanException(new ServiceNotFoundException(msg), msg);
}
// Argument type transformation according to signatures
for (int i = 0; i < sig.length; i++) {
// Some clients (e.g. RHQ) will pass the arguments as java.lang.String but we need some fields to be numbers
if (args[i] != null) {
if (log.isDebugEnabled())
log.debugf("Argument value before transformation: %s and its class: %s. " +
"For method.invoke we need it to be class: %s", args[i], args[i].getClass(), sig[i]);
if (sig[i].equals(int.class.getCanonicalName()) || sig[i].equals(Integer.class.getCanonicalName())) {
if ((args[i].getClass() != Integer.class) && (args[i].getClass() != int.class))
args[i] = Integer.parseInt((String) args[i]);
}
if (sig[i].equals(Long.class.getCanonicalName()) || sig[i].equals(long.class.getCanonicalName())) {
if ((args[i].getClass() != Long.class) && (args[i].getClass() != long.class))
args[i] = Long.parseLong((String) args[i]);
}
}
}
try {
Class>[] classes = new Class[sig.length];
for (int i = 0; i < classes.length; i++) {
classes[i] = ReflectionUtil.getClassForName(sig[i], null);
}
Method method = getObject().getClass().getMethod(opInfo.getName(), classes);
return method.invoke(getObject(), args);
} catch (Exception e) {
throw new MBeanException(new Exception(getRootCause(e)));
}
}
public Throwable getRootCause(Throwable throwable) {
Throwable cause;
while ((cause = throwable.getCause()) != null) {
throwable = cause;
}
return throwable;
}
private Attribute getNamedAttribute(String name) {
Attribute result = null;
if (name.equals(MBEAN_DESCRITION)) {
result = new Attribute(MBEAN_DESCRITION, mBeanMetadata.getDescription());
} else {
InvokableMBeanAttributeInfo i = atts.get(name);
if (i == null && name.length() > 0) {
// This is legacy. Earlier versions used an upper-case starting letter for *some* attributes.
Character firstChar = name.charAt(0);
if (Character.isUpperCase(firstChar)) {
name = name.replaceFirst(Character.toString(firstChar), Character.toString(Character.toLowerCase(firstChar)));
i = atts.get(name);
}
}
if (i != null) {
try {
result = new Attribute(name, i.invoke(null));
if (trace)
log.tracef("Attribute %s has r=%b,w=%b,is=%b and value %s",
name, i.getMBeanAttributeInfo().isReadable(), i.getMBeanAttributeInfo().isWritable(), i.getMBeanAttributeInfo().isIs(), result.getValue());
} catch (Exception e) {
log.debugf(e, "Exception while reading value of attribute %s", name);
}
} else {
log.queriedAttributeNotFound(name);
}
}
return result;
}
private void setNamedAttribute(Attribute attribute) throws MBeanException, AttributeNotFoundException {
if (log.isDebugEnabled())
log.debugf("Invoking set on attribute %s with value %s",
attribute.getName(), attribute.getValue());
String name = attribute.getName();
InvokableMBeanAttributeInfo i = atts.get(name);
if (i == null && name.length() > 0) {
// This is legacy. Earlier versions used an upper-case starting letter for *some* attributes.
Character firstChar = name.charAt(0);
if (Character.isUpperCase(firstChar)) {
name = name.replaceFirst(Character.toString(firstChar), Character.toString(Character.toLowerCase(firstChar)));
i = atts.get(name);
}
}
if (i != null) {
try {
i.invoke(attribute);
} catch (Exception e) {
log.errorWritingValueForAttribute(name, e);
throw new MBeanException(e, "Error invoking setter for attribute " + name);
}
} else {
log.couldNotInvokeSetOnAttribute(name, attribute.getValue());
throw new AttributeNotFoundException("Could not find attribute " + name);
}
}
private static abstract class InvokableMBeanAttributeInfo {
private final MBeanAttributeInfo attributeInfo;
public InvokableMBeanAttributeInfo(String name, String type, String description, boolean isReadable, boolean isWritable, boolean isIs) {
attributeInfo = new MBeanAttributeInfo(name, type, description, isReadable, isWritable, isIs);
}
public abstract Object invoke(Attribute a) throws IllegalAccessException, InvocationTargetException;
public MBeanAttributeInfo getMBeanAttributeInfo() {
return attributeInfo;
}
}
private static class InvokableFieldBasedMBeanAttributeInfo extends InvokableMBeanAttributeInfo {
private transient final Field field;
private transient final ResourceDMBean resource;
public InvokableFieldBasedMBeanAttributeInfo(String name, String type, String description, boolean isReadable, boolean isWritable, boolean isIs, Field field, ResourceDMBean resource) {
super(name, type, description, isReadable, isWritable, isIs);
this.field = field;
this.resource = resource;
}
@Override
public Object invoke(Attribute a) throws IllegalAccessException {
if (!Modifier.isPublic(field.getModifiers())) field.setAccessible(true);
if (a == null) {
return field.get(resource.getObject());
} else {
field.set(resource.getObject(), a.getValue());
return null;
}
}
}
private static class InvokableSetterBasedMBeanAttributeInfo extends InvokableMBeanAttributeInfo {
private transient final Method setter;
private transient final Method getter;
private transient final ResourceDMBean resource;
public InvokableSetterBasedMBeanAttributeInfo(String name, String type, String description, boolean isReadable, boolean isWritable, boolean isIs, Method getter, Method setter, ResourceDMBean resource) {
super(name, type, description, isReadable, isWritable, isIs);
this.setter = setter;
this.getter = getter;
this.resource = resource;
}
@Override
public Object invoke(Attribute a) throws IllegalAccessException, InvocationTargetException {
if (a == null) {
if (!Modifier.isPublic(getter.getModifiers())) getter.setAccessible(true);
return getter.invoke(resource.getObject(), null);
} else {
if (!Modifier.isPublic(setter.getModifiers())) setter.setAccessible(true);
return setter.invoke(resource.getObject(), a.getValue());
}
}
}
public String getObjectName() {
String s = mBeanMetadata.getJmxObjectName();
return (s != null && s.trim().length() > 0) ? s : objectClass.getSimpleName();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy