org.elasticsearch.jmx.ResourceDMBean Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of elasticsearch Show documentation
Show all versions of elasticsearch Show documentation
Open Source, Distributed, RESTful Search Engine
/*
* Licensed to Elastic Search and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Elastic Search licenses this
* file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.jmx;
import org.elasticsearch.util.Classes;
import org.elasticsearch.util.MapBuilder;
import org.elasticsearch.util.Preconditions;
import org.elasticsearch.util.Strings;
import org.elasticsearch.util.collect.ImmutableList;
import org.elasticsearch.util.collect.ImmutableMap;
import org.elasticsearch.util.logging.ESLogger;
import javax.management.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.elasticsearch.util.MapBuilder.*;
/**
* @author kimchy (Shay Banon)
*/
public class ResourceDMBean implements DynamicMBean {
private static final Class[] primitives = {int.class, byte.class, short.class, long.class,
float.class, double.class, boolean.class, char.class};
private final ESLogger logger;
private final Object obj;
private final String objectName;
private final String groupName;
private final String fullObjectName;
private final String description;
private final MBeanAttributeInfo[] attributesInfo;
private final MBeanOperationInfo[] operationsInfo;
private final MBeanInfo mBeanInfo;
private final ImmutableMap attributes;
private final ImmutableList operations;
public ResourceDMBean(Object instance, ESLogger logger) {
Preconditions.checkNotNull(instance, "Cannot make an MBean wrapper for null instance");
this.obj = instance;
this.logger = logger;
MapBuilder attributesBuilder = newMapBuilder();
List operationsBuilder = new ArrayList();
MBean mBean = obj.getClass().getAnnotation(MBean.class);
this.groupName = findGroupName();
if (mBean != null && Strings.hasLength(mBean.objectName())) {
objectName = mBean.objectName();
} else {
if (Strings.hasLength(groupName)) {
// we have something in the group object name, don't put anything in the object name
objectName = "";
} else {
objectName = obj.getClass().getSimpleName();
}
}
StringBuilder sb = new StringBuilder(groupName);
if (Strings.hasLength(groupName) && Strings.hasLength(objectName)) {
sb.append(",");
}
sb.append(objectName);
this.fullObjectName = sb.toString();
this.description = findDescription();
findFields(attributesBuilder);
findMethods(attributesBuilder, operationsBuilder);
this.attributes = attributesBuilder.immutableMap();
this.operations = ImmutableList.copyOf(operationsBuilder);
attributesInfo = new MBeanAttributeInfo[attributes.size()];
int i = 0;
MBeanAttributeInfo info;
for (AttributeEntry entry : attributes.values()) {
info = entry.getInfo();
attributesInfo[i++] = info;
if (logger.isInfoEnabled()) {
logger.trace("Attribute " + info.getName() + "[r=" + info.isReadable() + ",w="
+ info.isWritable() + ",is=" + info.isIs() + ",type=" + info.getType() + "]");
}
}
operationsInfo = new MBeanOperationInfo[operations.size()];
operations.toArray(operationsInfo);
if (logger.isTraceEnabled()) {
if (operations.size() > 0)
logger.trace("Operations are:");
for (MBeanOperationInfo op : operationsInfo) {
logger.trace("Operation " + op.getReturnType() + " " + op.getName());
}
}
this.mBeanInfo = new MBeanInfo(getObject().getClass().getCanonicalName(), description, attributesInfo, null, operationsInfo, null);
}
public MBeanInfo getMBeanInfo() {
return mBeanInfo;
}
public synchronized 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: " + attributes.keySet());
}
return attr.getValue();
}
public synchronized void setAttribute(Attribute attribute) {
if (attribute == null || attribute.getName() == null)
throw new NullPointerException("Invalid attribute requested " + attribute);
setNamedAttribute(attribute);
}
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 {
logger.warn("Did not find attribute " + name);
}
}
return al;
}
public synchronized AttributeList setAttributes(AttributeList list) {
AttributeList results = new AttributeList();
for (Object aList : list) {
Attribute attr = (Attribute) aList;
if (setNamedAttribute(attr)) {
results.add(attr);
} else {
if (logger.isWarnEnabled()) {
logger.warn("Failed to update attribute name " + attr.getName() + " with value "
+ attr.getValue());
}
}
}
return results;
}
public Object invoke(String name, Object[] args, String[] sig) throws MBeanException,
ReflectionException {
if (logger.isDebugEnabled()) {
logger.debug("Invoke method called on " + name);
}
MBeanOperationInfo opInfo = null;
for (MBeanOperationInfo op : operationsInfo) {
if (op.getName().equals(name)) {
opInfo = op;
break;
}
}
if (opInfo == null) {
final String msg = "Operation " + name + " not in ModelMBeanInfo";
throw new MBeanException(new ServiceNotFoundException(msg), msg);
}
try {
Class[] classes = new Class[sig.length];
for (int i = 0; i < classes.length; i++) {
classes[i] = getClassForName(sig[i]);
}
Method method = getObject().getClass().getMethod(name, classes);
return method.invoke(getObject(), args);
} catch (Exception e) {
throw new MBeanException(e);
}
}
Object getObject() {
return obj;
}
private Class getClassForName(String name) throws ClassNotFoundException {
try {
return Classes.getDefaultClassLoader().loadClass(name);
} catch (ClassNotFoundException cnfe) {
// Could be a primitive - let's check
for (Class primitive : primitives) {
if (name.equals(primitive.getName())) {
return primitive;
}
}
}
throw new ClassNotFoundException("Class " + name + " cannot be found");
}
private String findGroupName() {
Class objClass = getObject().getClass();
while (objClass != Object.class) {
Method[] methods = objClass.getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(ManagedGroupName.class)) {
try {
method.setAccessible(true);
return (String) method.invoke(getObject());
} catch (Exception e) {
logger.warn("Failed to get group name for [" + getObject() + "]", e);
}
}
}
objClass = objClass.getSuperclass();
}
return "";
}
private String findDescription() {
MBean mbean = getObject().getClass().getAnnotation(MBean.class);
if (mbean != null && mbean.description() != null && mbean.description().trim().length() > 0) {
return mbean.description();
}
return "";
}
private void findMethods(MapBuilder attributesBuilder, List ops) {
// find all methods but don't include methods from Object class
List methods = new ArrayList(Arrays.asList(getObject().getClass().getMethods()));
List objectMethods = new ArrayList(Arrays.asList(Object.class.getMethods()));
methods.removeAll(objectMethods);
for (Method method : methods) {
// does method have @ManagedAttribute annotation?
ManagedAttribute attr = method.getAnnotation(ManagedAttribute.class);
if (attr != null) {
String methodName = method.getName();
if (!methodName.startsWith("get") && !methodName.startsWith("set") && !methodName.startsWith("is")) {
if (logger.isWarnEnabled())
logger.warn("method name " + methodName
+ " doesn't start with \"get\", \"set\", or \"is\""
+ ", but is annotated with @ManagedAttribute: will be ignored");
} else {
MBeanAttributeInfo info;
String attributeName = null;
boolean writeAttribute = false;
if (isSetMethod(method)) { // setter
attributeName = methodName.substring(3);
info = new MBeanAttributeInfo(attributeName, method.getParameterTypes()[0]
.getCanonicalName(), attr.description(), true, true, false);
writeAttribute = true;
} else { // getter
if (method.getParameterTypes().length == 0
&& method.getReturnType() != java.lang.Void.TYPE) {
boolean hasSetter = attributesBuilder.containsKey(attributeName);
// we found is method
if (methodName.startsWith("is")) {
attributeName = methodName.substring(2);
info = new MBeanAttributeInfo(attributeName, method.getReturnType()
.getCanonicalName(), attr.description(), true, hasSetter, true);
} else {
// this has to be get
attributeName = methodName.substring(3);
info = new MBeanAttributeInfo(attributeName, method.getReturnType()
.getCanonicalName(), attr.description(), true, hasSetter, false);
}
} else {
if (logger.isWarnEnabled()) {
logger.warn("Method " + method.getName()
+ " must have a valid return type and zero parameters");
}
continue;
}
}
AttributeEntry ae = attributesBuilder.get(attributeName);
// is it a read method?
if (!writeAttribute) {
// we already have annotated field as read
if (ae instanceof FieldAttributeEntry && ae.getInfo().isReadable()) {
logger.warn("not adding annotated method " + method
+ " since we already have read attribute");
}
// we already have annotated set method
else if (ae instanceof MethodAttributeEntry) {
MethodAttributeEntry mae = (MethodAttributeEntry) ae;
if (mae.hasSetMethod()) {
attributesBuilder.put(attributeName, new MethodAttributeEntry(mae.getInfo(), mae
.getSetMethod(), method));
}
} // we don't have such entry
else {
attributesBuilder.put(attributeName, new MethodAttributeEntry(info, null, method));
}
}// is it a set method?
else {
if (ae instanceof FieldAttributeEntry) {
// we already have annotated field as write
if (ae.getInfo().isWritable()) {
logger.warn("Not adding annotated method " + methodName
+ " since we already have writable attribute");
} else {
// we already have annotated field as read
// lets make the field writable
Field f = ((FieldAttributeEntry) ae).getField();
MBeanAttributeInfo i = new MBeanAttributeInfo(ae.getInfo().getName(),
f.getType().getCanonicalName(), attr.description(), true,
!Modifier.isFinal(f.getModifiers()), false);
attributesBuilder.put(attributeName, new FieldAttributeEntry(i, f));
}
}
// we already have annotated getOrIs method
else if (ae instanceof MethodAttributeEntry) {
MethodAttributeEntry mae = (MethodAttributeEntry) ae;
if (mae.hasIsOrGetMethod()) {
attributesBuilder.put(attributeName, new MethodAttributeEntry(info, method, mae
.getIsOrGetMethod()));
}
} // we don't have such entry
else {
attributesBuilder.put(attributeName, new MethodAttributeEntry(info, method, null));
}
}
}
} else if (method.isAnnotationPresent(ManagedOperation.class)) {
ManagedOperation op = method.getAnnotation(ManagedOperation.class);
String attName = method.getName();
if (isSetMethod(method) || isGetMethod(method)) {
attName = attName.substring(3);
} else if (isIsMethod(method)) {
attName = attName.substring(2);
}
// expose unless we already exposed matching attribute field
boolean isAlreadyExposed = attributesBuilder.containsKey(attName);
if (!isAlreadyExposed) {
ops.add(new MBeanOperationInfo(op != null ? op.description() : "", method));
}
}
}
}
private boolean isSetMethod(Method method) {
return (method.getName().startsWith("set") && method.getParameterTypes().length == 1 && method.getReturnType() == java.lang.Void.TYPE);
}
private boolean isGetMethod(Method method) {
return (method.getParameterTypes().length == 0 && method.getReturnType() != java.lang.Void.TYPE && method.getName().startsWith("get"));
}
private boolean isIsMethod(Method method) {
return (method.getParameterTypes().length == 0 && (method.getReturnType() == boolean.class || method.getReturnType() == Boolean.class) && method.getName().startsWith("is"));
}
private void findFields(MapBuilder attributesBuilder) {
// traverse class hierarchy and find all annotated fields
for (Class clazz = getObject().getClass(); clazz != null; clazz = clazz.getSuperclass()) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
ManagedAttribute attr = field.getAnnotation(ManagedAttribute.class);
if (attr != null) {
String fieldName = renameToJavaCodingConvention(field.getName());
MBeanAttributeInfo info = new MBeanAttributeInfo(fieldName, field.getType().getCanonicalName(),
attr.description(), true, !Modifier.isFinal(field.getModifiers()) && attr.writable(), false);
attributesBuilder.put(fieldName, new FieldAttributeEntry(info, field));
}
}
}
}
private Attribute getNamedAttribute(String name) {
Attribute result = null;
AttributeEntry entry = attributes.get(name);
if (entry != null) {
MBeanAttributeInfo i = entry.getInfo();
try {
result = new Attribute(name, entry.invoke(null));
if (logger.isDebugEnabled())
logger.debug("Attribute " + name + " has r=" + i.isReadable() + ",w="
+ i.isWritable() + ",is=" + i.isIs() + " and value "
+ result.getValue());
} catch (Exception e) {
logger.debug("Exception while reading value of attribute " + name, e);
}
} else {
logger.warn("Did not find queried attribute with name " + name);
}
return result;
}
private boolean setNamedAttribute(Attribute attribute) {
boolean result = false;
if (logger.isDebugEnabled())
logger.debug("Invoking set on attribute " + attribute.getName() + " with value " + attribute.getValue());
AttributeEntry entry = attributes.get(attribute.getName());
if (entry != null) {
try {
entry.invoke(attribute);
result = true;
} catch (Exception e) {
logger.warn("Exception while writing value for attribute " + attribute.getName(), e);
}
} else {
logger.warn("Could not invoke set on attribute " + attribute.getName() + " with value "
+ attribute.getValue());
}
return result;
}
private String renameToJavaCodingConvention(String fieldName) {
if (fieldName.contains("_")) {
Pattern p = Pattern.compile("_.");
Matcher m = p.matcher(fieldName);
StringBuffer sb = new StringBuffer();
while (m.find()) {
m.appendReplacement(sb, fieldName.substring(m.end() - 1, m.end()).toUpperCase());
}
m.appendTail(sb);
char first = sb.charAt(0);
if (Character.isLowerCase(first)) {
sb.setCharAt(0, Character.toUpperCase(first));
}
return sb.toString();
} else {
if (Character.isLowerCase(fieldName.charAt(0))) {
return fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
} else {
return fieldName;
}
}
}
private class MethodAttributeEntry implements AttributeEntry {
final MBeanAttributeInfo info;
final Method isOrGetmethod;
final Method setMethod;
public MethodAttributeEntry(final MBeanAttributeInfo info, final Method setMethod,
final Method isOrGetMethod) {
super();
this.info = info;
this.setMethod = setMethod;
this.isOrGetmethod = isOrGetMethod;
}
public Object invoke(Attribute a) throws Exception {
if (a == null && isOrGetmethod != null)
return isOrGetmethod.invoke(getObject());
else if (a != null && setMethod != null)
return setMethod.invoke(getObject(), a.getValue());
else
return null;
}
public MBeanAttributeInfo getInfo() {
return info;
}
public boolean hasIsOrGetMethod() {
return isOrGetmethod != null;
}
public boolean hasSetMethod() {
return setMethod != null;
}
public Method getIsOrGetMethod() {
return isOrGetmethod;
}
public Method getSetMethod() {
return setMethod;
}
}
private class FieldAttributeEntry implements AttributeEntry {
private final MBeanAttributeInfo info;
private final Field field;
public FieldAttributeEntry(final MBeanAttributeInfo info, final Field field) {
super();
this.info = info;
this.field = field;
if (!field.isAccessible()) {
field.setAccessible(true);
}
}
public Field getField() {
return field;
}
public Object invoke(Attribute a) throws Exception {
if (a == null) {
return field.get(getObject());
} else {
field.set(getObject(), a.getValue());
return null;
}
}
public MBeanAttributeInfo getInfo() {
return info;
}
}
private interface AttributeEntry {
public Object invoke(Attribute a) throws Exception;
public MBeanAttributeInfo getInfo();
}
public boolean isManagedResource() {
return !attributes.isEmpty() || !operations.isEmpty();
}
public String getFullObjectName() {
return this.fullObjectName;
}
public String getObjectName() {
return this.objectName;
}
public String getGroupName() {
return this.groupName;
}
}