All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.jmx.ResourceDMBean Maven / Gradle / Ivy

There is a newer version: 8.13.4
Show newest version
/*
 * 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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy