Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.j256.simplejmx.server.ReflectionMbean Maven / Gradle / Ivy
package com.j256.simplejmx.server;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
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 com.j256.simplejmx.common.JmxAttributeField;
import com.j256.simplejmx.common.JmxAttributeFieldInfo;
import com.j256.simplejmx.common.JmxAttributeMethod;
import com.j256.simplejmx.common.JmxAttributeMethodInfo;
import com.j256.simplejmx.common.JmxOperation;
import com.j256.simplejmx.common.JmxOperationInfo;
import com.j256.simplejmx.common.JmxResource;
/**
* This wraps an object that has been registered in the server using {@link JmxServer#register(Object)}. We wrap the
* object so we can expose its attributes and operations using annotations and reflection. This handles the JMX server
* calls to attributes and operations by calling through the delegation object.
*
* @author graywatson
*/
public class ReflectionMbean implements DynamicMBean {
private final Object target;
private final String description;
private final Map attributeMethodMap = new HashMap();
private final Map operationMethodMap = new HashMap();
private final Map attributeFieldMap = new HashMap();
private final MBeanInfo mbeanInfo;
/**
* Create a mbean associated with a target object that must have a {@link JmxResource} annotation.
*/
public ReflectionMbean(Object target, String description) {
this(target, description, null, null, null, false);
}
/**
* Create a mbean associated with a wrapped object that exposes all public fields and methods.
*/
public ReflectionMbean(PublishAllBeanWrapper wrapper) {
this(wrapper.getTarget(), null, wrapper.getAttributeFieldInfos(), wrapper.getAttributeMethodInfos(),
wrapper.getOperationInfos(), true);
}
/**
* Create a mbean associated with a target object with user provided attribute and operation information.
*/
public ReflectionMbean(Object target, String description, JmxAttributeFieldInfo[] attributeFieldInfos,
JmxAttributeMethodInfo[] attributeMethodInfos, JmxOperationInfo[] operationInfos, boolean ignoreErrors) {
this.target = target;
this.description = preprocessDescription(target, description);
this.mbeanInfo = buildMbeanInfo(attributeFieldInfos, attributeMethodInfos, operationInfos, ignoreErrors);
}
@Override
public MBeanInfo getMBeanInfo() {
return mbeanInfo;
}
@Override
public Object getAttribute(String attributeName) throws AttributeNotFoundException, ReflectionException {
AttributeMethodInfo methodInfo = attributeMethodMap.get(attributeName);
if (methodInfo == null) {
AttributeFieldInfo fieldInfo = attributeFieldMap.get(attributeName);
if (fieldInfo == null || !fieldInfo.isGetter) {
throwUnknownAttributeException(attributeName);
}
try {
// get the value by using reflection on the Field
return fieldInfo.field.get(target);
} catch (Exception e) {
throw new ReflectionException(e, "Invoking getter attribute on field " + fieldInfo.field.getName()
+ " on " + target.getClass() + " threw exception");
}
} else {
if (methodInfo.getterMethod == null) {
throwUnknownAttributeException(attributeName);
}
try {
// get the value by calling the method
return methodInfo.getterMethod.invoke(target);
} catch (Exception e) {
throw new ReflectionException(e, "Invoking getter attribute method "
+ methodInfo.getterMethod.getName() + " on " + target.getClass() + " threw exception");
}
}
}
@Override
public AttributeList getAttributes(String[] attributeNames) {
AttributeList returnList = new AttributeList();
for (String name : attributeNames) {
try {
returnList.add(new Attribute(name, getAttribute(name)));
} catch (Exception e) {
returnList.add(new Attribute(name, "Getting attribute threw: " + e.getMessage()));
}
}
return returnList;
}
@Override
public void setAttribute(Attribute attribute) throws AttributeNotFoundException, ReflectionException {
AttributeMethodInfo methodInfo = attributeMethodMap.get(attribute.getName());
if (methodInfo == null) {
AttributeFieldInfo fieldInfo = attributeFieldMap.get(attribute.getName());
if (fieldInfo == null || !fieldInfo.isSetter) {
throwUnknownAttributeException(attribute.getName());
}
try {
fieldInfo.field.set(target, attribute.getValue());
} catch (Exception e) {
throw new ReflectionException(e, "Invoking setter attribute on field " + fieldInfo.field.getName()
+ " on " + target.getClass() + " threw exception");
}
} else {
if (methodInfo.setterMethod == null) {
throwUnknownAttributeException(attribute.getName());
}
try {
methodInfo.setterMethod.invoke(target, attribute.getValue());
} catch (Exception e) {
throw new ReflectionException(e, "Invoking setter attribute method "
+ methodInfo.setterMethod.getName() + " on " + target.getClass() + " threw exception");
}
}
}
@Override
public AttributeList setAttributes(AttributeList attributes) {
AttributeList returnList = new AttributeList(attributes.size());
for (Attribute attribute : attributes.asList()) {
String name = attribute.getName();
try {
setAttribute(attribute);
returnList.add(new Attribute(name, getAttribute(name)));
} catch (Exception e) {
returnList.add(new Attribute(name, e.getMessage()));
}
}
return returnList;
}
@Override
public Object invoke(String actionName, Object[] params, String[] signatureTypes) throws MBeanException,
ReflectionException {
Method method = operationMethodMap.get(new NameParams(actionName, signatureTypes));
if (method == null) {
throw new MBeanException(new IllegalArgumentException("Unknown action '" + actionName
+ "' with parameter types " + Arrays.toString(signatureTypes)));
}
try {
return method.invoke(target, params);
} catch (Exception e) {
throw new ReflectionException(e, "Invoking operation method " + method.getName() + " on "
+ target.getClass() + " threw exception");
}
}
private static String preprocessDescription(Object target, String description) {
if (description == null) {
return "Information about " + target.getClass();
} else {
return description;
}
}
/**
* Build our JMX information object by using reflection.
*/
private MBeanInfo buildMbeanInfo(JmxAttributeFieldInfo[] attributeFieldInfos,
JmxAttributeMethodInfo[] attributeMethodInfos, JmxOperationInfo[] operationInfos, boolean ignoreErrors) {
// NOTE: setup the maps that track previous class configuration
Map attributeFieldInfoMap = null;
if (attributeFieldInfos != null) {
attributeFieldInfoMap = new HashMap();
for (JmxAttributeFieldInfo info : attributeFieldInfos) {
attributeFieldInfoMap.put(info.getFieldName(), info);
}
}
Map attributeMethodInfoMap = null;
if (attributeMethodInfos != null) {
attributeMethodInfoMap = new HashMap();
for (JmxAttributeMethodInfo info : attributeMethodInfos) {
attributeMethodInfoMap.put(info.getMethodName(), info);
}
}
Map attributeOperationInfoMap = null;
if (operationInfos != null) {
attributeOperationInfoMap = new HashMap();
for (JmxOperationInfo info : operationInfos) {
attributeOperationInfoMap.put(info.getMethodName(), info);
}
}
Set attributeNameSet = new HashSet();
List attributes = new ArrayList();
// NOTE: methods override fields so subclasses can stop exposing of fields
discoverAttributeMethods(attributeMethodInfoMap, attributeNameSet, attributes, ignoreErrors);
discoverAttributeFields(attributeFieldInfoMap, attributeNameSet, attributes);
List operations = discoverOperations(attributeOperationInfoMap);
return new MBeanInfo(target.getClass().getName(), description,
attributes.toArray(new MBeanAttributeInfo[attributes.size()]), null,
operations.toArray(new MBeanOperationInfo[operations.size()]), null);
}
/**
* Find attribute methods from our object that will be exposed via JMX.
*/
private void discoverAttributeMethods(Map attributeMethodInfoMap,
Set attributeNameSet, List attributes, boolean ignoreErrors) {
for (Class> clazz = target.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) {
discoverAttributeMethods(attributeMethodInfoMap, attributeNameSet, attributes, ignoreErrors, clazz);
}
/*
* we have to go back and post process the attribute-method-map because the getter and setter methods change the
* method-info multiple times.
*/
for (AttributeMethodInfo methodInfo : attributeMethodMap.values()) {
attributes.add(new MBeanAttributeInfo(methodInfo.varName, methodInfo.type.getName(),
methodInfo.description, (methodInfo.getterMethod != null), (methodInfo.setterMethod != null),
methodInfo.isIs()));
attributeNameSet.add(methodInfo.varName);
}
}
private void discoverAttributeMethods(Map attributeMethodInfoMap,
Set attributeNameSet, List attributes, boolean ignoreErrors, Class> clazz) {
for (Method method : clazz.getMethods()) {
JmxAttributeMethod jmxAttribute = method.getAnnotation(JmxAttributeMethod.class);
JmxAttributeMethodInfo attributeMethodInfo = null;
if (jmxAttribute == null) {
// skip it if no annotation
if (attributeMethodInfoMap != null) {
// was this attribute method already configured?
attributeMethodInfo = attributeMethodInfoMap.get(method.getName());
}
if (attributeMethodInfo == null) {
continue;
}
} else {
attributeMethodInfo = new JmxAttributeMethodInfo(method.getName(), jmxAttribute);
jmxAttribute = null;
}
try {
discoverAttributeMethod(attributeNameSet, method, attributeMethodInfo);
} catch (IllegalArgumentException iae) {
if (!ignoreErrors) {
throw iae;
}
}
}
}
private void discoverAttributeMethod(Set attributeNameSet, Method method,
JmxAttributeMethodInfo attributeMethodInfo) {
String methodName = method.getName();
boolean isIs;
if (methodName.startsWith("is")) {
if (method.getReturnType() != boolean.class && method.getReturnType() != Boolean.class) {
throw new IllegalArgumentException("Method '" + method
+ "' starts with 'is' but does not return a boolean or Boolean class");
}
isIs = true;
} else {
isIs = false;
}
String varName = buildMethodSuffix(method, methodName, isIs);
if (attributeNameSet.contains(varName)) {
return;
}
AttributeMethodInfo methodInfo = attributeMethodMap.get(varName);
if (isIs || methodName.startsWith("get")) {
if (method.getParameterTypes().length != 0) {
throw new IllegalArgumentException("Method '" + method + "' starts with 'get' but has arguments");
}
if (method.getReturnType() == void.class) {
throw new IllegalArgumentException("Method '" + method
+ "' starts with 'get' but does not return anything");
}
if (methodInfo == null) {
attributeMethodMap.put(varName, new AttributeMethodInfo(varName, attributeMethodInfo.getDescription(),
method, null));
} else {
// setter must have already started our method-info, add the getter to it
methodInfo.getterMethod = method;
}
} else if (methodName.startsWith("set")) {
if (method.getParameterTypes().length != 1) {
throw new IllegalArgumentException("Method '" + method
+ "' starts with 'set' but does not have 1 argument");
}
if (method.getReturnType() != void.class) {
throw new IllegalArgumentException("Method '" + method + "' starts with 'set' but does not return void");
}
if (methodInfo == null) {
attributeMethodMap.put(varName, new AttributeMethodInfo(varName, attributeMethodInfo.getDescription(),
null, method));
} else {
// getter must have already started our method-info, add the setter to it
methodInfo.setterMethod = method;
}
} else {
throw new IllegalArgumentException("Method '" + method
+ "' is marked as an attribute but does not start with 'get' or 'set'");
}
}
/**
* Find attribute methods from our object that will be exposed via JMX.
*/
private void discoverAttributeFields(Map attributeFieldInfoMap,
Set attributeNameSet, List attributes) {
for (Class> clazz = target.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) {
discoverAttributeFields(attributeFieldInfoMap, attributeNameSet, attributes, clazz);
}
}
private void discoverAttributeFields(Map attributeFieldInfoMap,
Set attributeNameSet, List attributes, Class> clazz) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
String fieldName = field.getName();
if (attributeNameSet.contains(fieldName)) {
continue;
}
JmxAttributeField attributeField = field.getAnnotation(JmxAttributeField.class);
JmxAttributeFieldInfo attributeFieldInfo = null;
if (attributeField == null) {
if (attributeFieldInfoMap != null) {
// was this attribute field already configured?
attributeFieldInfo = attributeFieldInfoMap.get(fieldName);
}
if (attributeFieldInfo == null) {
continue;
}
} else {
attributeFieldInfo = new JmxAttributeFieldInfo(fieldName, attributeField);
attributeField = null;
}
if (!field.isAccessible()) {
field.setAccessible(true);
}
attributeFieldMap.put(fieldName, new AttributeFieldInfo(field, attributeFieldInfo.isReadible(),
attributeFieldInfo.isWritable()));
String description = attributeFieldInfo.getDescription();
if (isEmpty(description)) {
description = fieldName + " attribute";
}
boolean isIs;
if (fieldName.startsWith("is") && (field.getType() == boolean.class || field.getType() == Boolean.class)) {
isIs = true;
} else {
isIs = false;
}
attributes.add(new MBeanAttributeInfo(fieldName, field.getType().getName(), description,
attributeFieldInfo.isReadible(), attributeFieldInfo.isWritable(), isIs));
attributeNameSet.add(fieldName);
}
}
/**
* Find operation methods from our object that will be exposed via JMX.
*/
private List discoverOperations(Map attributeOperationInfoMap) {
Set methodSignatureSet = new HashSet();
List operations = new ArrayList(operationMethodMap.size());
for (Class> clazz = target.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) {
discoverOperations(attributeOperationInfoMap, methodSignatureSet, operations, clazz);
}
return operations;
}
private void discoverOperations(Map attributeOperationInfoMap,
Set methodSignatureSet, List operations, Class> clazz) {
for (Method method : clazz.getMethods()) {
MethodSignature methodSignature = new MethodSignature(method);
if (methodSignatureSet.contains(methodSignature)) {
continue;
}
String methodName = method.getName();
JmxOperation jmxOperation = method.getAnnotation(JmxOperation.class);
JmxOperationInfo operationInfo = null;
if (jmxOperation == null) {
if (attributeOperationInfoMap != null) {
// was this operation already configured?
operationInfo = attributeOperationInfoMap.get(methodName);
}
if (operationInfo == null) {
continue;
}
} else {
operationInfo = new JmxOperationInfo(methodName, jmxOperation);
jmxOperation = null;
}
if (methodName.startsWith("get") || methodName.startsWith("is") || methodName.startsWith("set")) {
throw new IllegalArgumentException("Operation method " + method
+ " cannot start with 'get', 'is', or 'set'. Did you use the wrong annotation?");
}
Class>[] types = method.getParameterTypes();
String[] stringTypes = new String[types.length];
for (int i = 0; i < types.length; i++) {
stringTypes[i] = types[i].getName();
}
NameParams nameParams = new NameParams(methodName, stringTypes);
MBeanParameterInfo[] parameterInfos = buildOperationParameterInfo(method, operationInfo);
operationMethodMap.put(nameParams, method);
String description = operationInfo.getDescription();
if (isEmpty(description)) {
description = methodName + " operation";
}
operations.add(new MBeanOperationInfo(methodName, description, parameterInfos, method.getReturnType()
.getName(), operationInfo.getAction().getActionValue()));
methodSignatureSet.add(methodSignature);
}
}
/**
* Build our parameter information for an operation.
*/
private MBeanParameterInfo[] buildOperationParameterInfo(Method method, JmxOperationInfo operationInfo) {
Class>[] types = method.getParameterTypes();
MBeanParameterInfo[] parameterInfos = new MBeanParameterInfo[types.length];
String[] parameterNames = operationInfo.getParameterNames();
String[] parameterDescriptions = operationInfo.getParameterDescriptions();
for (int i = 0; i < types.length; i++) {
String parameterName;
if (parameterNames == null || i >= parameterNames.length) {
parameterName = "p" + (i + 1);
} else {
parameterName = parameterNames[i];
}
String typeName = types[i].getName();
String description;
if (parameterDescriptions == null || i >= parameterDescriptions.length) {
description = "parameter #" + (i + 1) + " of type: " + typeName;
} else {
description = parameterDescriptions[i];
}
parameterInfos[i] = new MBeanParameterInfo(parameterName, typeName, description);
}
return parameterInfos;
}
private String buildMethodSuffix(Method method, String methodName, boolean isIs) {
if (isIs) {
if (methodName.length() < 3) {
throw new IllegalArgumentException("Method '" + methodName + "' has a name that is too short");
}
return Character.toLowerCase(methodName.charAt(2)) + methodName.substring(3);
} else {
if (methodName.length() < 4) {
throw new IllegalArgumentException("Method '" + methodName + "' has a name that is too short");
}
return Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
}
}
/**
* We do this to standardize our exceptions around unknown attributes.
*/
private void throwUnknownAttributeException(String attributeName) throws AttributeNotFoundException {
throw new AttributeNotFoundException("Unknown attribute " + attributeName);
}
private static boolean isEmpty(String string) {
return string == null || string.trim().length() == 0;
}
/**
* Key class for our hashmap to find matching methods based on name and parameter list.
*/
private static class NameParams {
String name;
String[] paramTypes;
public NameParams(String name, String[] paramTypes) {
this.name = name;
this.paramTypes = paramTypes;
}
@Override
public int hashCode() {
int hashCode = 31 * (31 + name.hashCode());
if (paramTypes != null) {
hashCode += Arrays.hashCode(paramTypes);
}
return hashCode;
}
@Override
public boolean equals(Object obj) {
if (obj == null || getClass() != obj.getClass()) {
return false;
}
NameParams other = (NameParams) obj;
if (!this.name.equals(other.name)) {
return false;
}
return Arrays.equals(this.paramTypes, other.paramTypes);
}
}
/**
* Information about attribute methods.
*/
private static class AttributeMethodInfo {
final String varName;
final String description;
Method getterMethod;
Method setterMethod;
final Class> type;
public AttributeMethodInfo(String varName, String description, Method getterMethod, Method setterMethod) {
this.varName = varName;
if (isEmpty(description)) {
this.description = varName + " attribute";
} else {
this.description = description;
}
this.getterMethod = getterMethod;
this.setterMethod = setterMethod;
if (getterMethod == null) {
type = setterMethod.getParameterTypes()[0];
} else {
type = getterMethod.getReturnType();
}
}
public boolean isIs() {
if (getterMethod != null && getterMethod.getName().startsWith("is")
&& (type == boolean.class || type == Boolean.class)) {
return true;
} else {
return false;
}
}
}
/**
* Information about attribute fields
*/
private static class AttributeFieldInfo {
final Field field;
final boolean isGetter;
final boolean isSetter;
public AttributeFieldInfo(Field field, boolean isGetter, boolean isSetter) {
this.field = field;
this.isGetter = isGetter;
this.isSetter = isSetter;
}
}
/**
* Method signature that matches the name and parameter-types. We don't care about return type because Java doesn't
* match methods using it.
*/
private static class MethodSignature {
final String name;
final Class>[] parameterTypes;
private MethodSignature(Method method) {
this.name = method.getName();
this.parameterTypes = method.getParameterTypes();
}
@Override
public int hashCode() {
final int prime = 31;
int result = prime + name.hashCode();
result = prime * result + Arrays.hashCode(parameterTypes);
return result;
}
@Override
public boolean equals(Object obj) {
if (obj == null || getClass() != obj.getClass()) {
return false;
}
MethodSignature other = (MethodSignature) obj;
return name.equals(other.name) && Arrays.equals(parameterTypes, other.parameterTypes);
}
}
}