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

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

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

The newest version!
package org.jgroups.jmx;

import org.jgroups.annotations.MBean;
import org.jgroups.annotations.ManagedAttribute;
import org.jgroups.annotations.ManagedOperation;
import org.jgroups.annotations.Property;
import org.jgroups.conf.AttributeType;
import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;
import org.jgroups.util.Util;

import javax.management.*;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.BiConsumer;
import java.util.function.Predicate;

/**
 * 
 * A DynamicMBean wrapping an annotated object instance and exposing attributes annotated with @ManagedAttribute and
 * operations annotated with @ManagedOperation.
 * 
 * @author Chris Mills
 * @author Vladimir Blagojevic
 * @author Bela Ban
 * @see org.jgroups.annotations.ManagedAttribute
 * @see org.jgroups.annotations.ManagedOperation
 * @see org.jgroups.annotations.MBean
 *
 */
public class ResourceDMBean implements DynamicMBean {
    protected static final Class[]              primitives= {int.class, byte.class, short.class, long.class,
                                                                float.class, double.class, boolean.class, char.class };

    protected static final List            OBJECT_METHODS; // all methods of java.lang.Object
    protected final boolean                        expose_all;
    protected final Log                            log=LogFactory.getLog(ResourceDMBean.class);
    protected final Object                         obj;
    protected List                         objs;
    protected final MBeanAttributeInfo[]           attrInfo;
    protected final MBeanOperationInfo[]           opInfo;
    protected final HashMap atts=new HashMap<>();
    protected final List       ops=new ArrayList<>();

    static {OBJECT_METHODS=new ArrayList<>(Arrays.asList(Object.class.getMethods()));}

    protected static final Predicate FILTER=obj -> obj.isAnnotationPresent(ManagedAttribute.class) ||
      (obj.isAnnotationPresent(Property.class) && obj.getAnnotation(Property.class).exposeAsManagedAttribute());


    public ResourceDMBean(Object instance) {
        if(instance == null)
            throw new NullPointerException("Cannot make an MBean wrapper for null instance");
        this.obj=instance;
        Class c=obj.getClass();
        expose_all=c.isAnnotationPresent(MBean.class) && c.getAnnotation(MBean.class).exposeAll();

        findFields(instance);
        findMethods(instance);
        fixFields(instance);

        List objects=Util.getComponents(instance);
        if(objects != null) {
            for(Object inst: objects) {
                if(inst != null) {
                    if(objs == null)
                        objs=new ArrayList<>();
                    objs.add(inst);
                    findFields(inst);
                    findMethods(inst);
                    fixFields(inst);
                }
            }
        }

        attrInfo=new MBeanAttributeInfo[atts.size()];
        int i=0;
        MBeanAttributeInfo info=null;
        for(AttributeEntry entry: atts.values()) {
            info=entry.info;
            attrInfo[i++]=info;
        }

        opInfo=new MBeanOperationInfo[ops.size()];
        ops.toArray(opInfo);
    }



    public MBeanInfo getMBeanInfo() {
        return new MBeanInfo(obj.getClass().getCanonicalName(), "DynamicMBean", attrInfo, null, opInfo, null);
    }

    public Object getAttribute(String name) {
        if(name == null || name.isEmpty())
            throw new NullPointerException("Invalid attribute requested " + name);
        Attribute attr=getNamedAttribute(name);
        return attr != null? attr.getValue() : null;
    }

    public void setAttribute(Attribute attribute) {
        if(attribute == null || attribute.getName() == null)
            throw new NullPointerException("Invalid attribute requested " + attribute);
        setNamedAttribute(attribute);
    }

    public AttributeList getAttributes(String[] names) {
        AttributeList al=new AttributeList();
        for(String name: names) {
            Attribute attr=getNamedAttribute(name);
            if(attr != null)
                al.add(attr);
            else
                log.warn("Did not find attribute " + name);
        }
        return al;
    }

    public AttributeList setAttributes(AttributeList list) {
        AttributeList results=new AttributeList();
        for(int i=0;i < list.size();i++) {
            Attribute attr=(Attribute)list.get(i);
            if(setNamedAttribute(attr))
                results.add(attr);
            else {
                if(log.isWarnEnabled())
                    log.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 {
        try {
            Class[] classes=new Class[sig.length];
            for(int i=0;i < classes.length;i++)
                classes[i]=getClassForName(sig[i]);
            Method method=null;
            if(objs != null) {
                for(Object o: objs) {
                    try {
                        method=o.getClass().getMethod(name, classes);
                    }
                    catch(Throwable t) {
                        continue;
                    }
                    return method.invoke(o, args);
                }
            }
            method=obj.getClass().getMethod(name, classes);
            return method.invoke(obj, args);
        }
        catch(Exception e) {
            throw new MBeanException(e);
        }
    }


    public static boolean isSetMethod(Method method) {
        return method.getParameterCount() == 1;
    }

    public static boolean isGetMethod(Method method) {
        return method.getParameterTypes().length == 0 && method.getReturnType() != Void.TYPE;
    }

    public static boolean isIsMethod(Method method) {
        return method.getParameterTypes().length == 0 &&
          (method.getReturnType() == boolean.class || method.getReturnType() == Boolean.class);
    }


    public static void dumpStats(Object obj, final Map map) {
        dumpStats(obj, "", map);
    }

    public static void dumpStats(Object obj, String prefix, final Map map) {
        BiConsumer field_func=(f,o) -> {
            String attr_name=null;
            try {
                f.setAccessible(true);
                Object value=f.get(o);
                attr_name=Util.getNameFromAnnotation(f);
                if(attr_name != null && !attr_name.trim().isEmpty())
                    attr_name=attr_name.trim();
                else
                    attr_name=f.getName();
                if(prefix != null && !prefix.isEmpty())
                    attr_name=prefix + "." + attr_name;
                map.put(attr_name, prettyPrint(value, f));
            }
            catch(Exception e) {
                throw new RuntimeException(String.format("could not read value of attribute \"%s\"", attr_name), e);
            }
        };
        BiConsumer getter_func=(m,o) -> {
            String method_name=null;
            if(!ResourceDMBean.isGetMethod(m))
                return;
            try {
                Object value=m.invoke(o);
                method_name=Util.getNameFromAnnotation(m);
                if(method_name != null && !method_name.trim().isEmpty())
                    method_name=method_name.trim();
                else {
                    String field_name=Util.methodNameToAttributeName(m.getName());
                    method_name=Util.attributeNameToMethodName(field_name);
                }
                String attributeName=Util.methodNameToAttributeName(method_name);
                if(prefix != null && !prefix.isEmpty())
                    attributeName=prefix + "." + attributeName;
                map.put(attributeName, prettyPrint(value, m));
            }
            catch(Exception e) {
                throw new RuntimeException(String.format("could not invoke getter method \"%s\"", method_name),e);
            }
        };
        Util.forAllFieldsAndMethods(obj, FILTER, field_func, getter_func);
    }


    @SuppressWarnings("MalformedFormatString")
    public static String prettyPrint(Object val, AccessibleObject a) {
        if(val == null) return "null";
        Class cl=val.getClass();
        AttributeType type=getType(a);
        if(type != null) {
            switch(type) {
                case BYTES:
                    return Util.printBytes(((Number)val).doubleValue());
                case TIME:
                    TimeUnit unit=getTimeUnit(a);
                    return Util.printTime(((Number)val).doubleValue(), unit);
                case SCALAR:
                    if(isNumeric(cl))
                        return String.format("%,d", val);
                    if(cl.equals(LongAdder.class))
                        return String.format("%,d", ((LongAdder)val).sum());
                    if(val instanceof Number)
                        return String.format("%,d", ((Number)val).longValue());
                    break;
            }
        }
        if(isNumeric(cl))
            return String.format("%d", val);
        if(isFractional(cl))
            return String.format("%,.2f", val);
        if(cl.equals(String[].class))
            return Arrays.toString((String[])val);
        return val.toString();
    }

    public static boolean isNumeric(Class cl) {
        return cl.equals(short.class) || cl.equals(Short.class) || cl.equals(int.class) || cl.equals(Integer.class)
          || cl.equals(long.class) || cl.equals(Long.class);
    }

    public static boolean isFractional(Class cl) {
        return cl.equals(float.class) || cl.equals(Float.class) || cl.equals(double.class) || cl.equals(Double.class);
    }


    protected static AttributeType getType(AccessibleObject ao) {
        Property prop=ao.getAnnotation(Property.class);
        if(prop != null)
            return prop.type();
        ManagedAttribute attr=ao.getAnnotation(ManagedAttribute.class);
        return attr != null? attr.type() : null;
    }

    protected static TimeUnit getTimeUnit(AccessibleObject ao) {
        Property prop=ao.getAnnotation(Property.class);
        if(prop != null)
            return prop.unit();
        ManagedAttribute attr=ao.getAnnotation(ManagedAttribute.class);
        return attr != null? attr.unit() : null;
    }


    protected static Class getClassForName(String name) throws ClassNotFoundException {
        try {
            return Class.forName(name);
        }
        catch(ClassNotFoundException cnfe) {
            //Could be a primitive - let's check
            for(int i=0;i < primitives.length;i++) {
                if(name.equals(primitives[i].getName())) {
                    return primitives[i];
                }
            }
        }
        throw new ClassNotFoundException("Class " + name + " cannot be found");
    }

    protected void findMethods(Object instance) {
        // find all methods but don't include methods from Object class
        List methods = new ArrayList<>(Arrays.asList(instance.getClass().getMethods()));
        methods.removeAll(OBJECT_METHODS);

        for(Method method: methods) {
            // does method have @ManagedAttribute annotation?
            if(method.isAnnotationPresent(ManagedAttribute.class) || method.isAnnotationPresent(Property.class)) {
                exposeManagedAttribute(method, instance);
            }
            //or @ManagedOperation
            else if (method.isAnnotationPresent(ManagedOperation.class) || expose_all){
                ManagedOperation op=method.getAnnotation(ManagedOperation.class);
                ops.add(new MBeanOperationInfo(op != null? op.description() : "",method));
            }
        }
    }

    /** Provides field-based getter and/or setters for all attributes in attrs if not present */
    protected void fixFields(Object instance) {
        for(AttributeEntry attr: atts.values()) {
            if(attr.getter == null)
                attr.getter=findGetter(instance, attr.name);
            if(attr.setter == null)
                attr.setter=findSetter(instance, attr.name);
            if(attr.setter == null)
                attr.setter=new NoopAccessor();
        }
    }



    protected void exposeManagedAttribute(Method method, Object instance) {
        String           methodName=method.getName();
        ManagedAttribute attr_annotation=method.getAnnotation(ManagedAttribute.class);
        Property         prop=method.getAnnotation(Property.class);

        boolean expose_prop=prop != null && prop.exposeAsManagedAttribute();
        boolean expose=attr_annotation != null || expose_prop;
        if(!expose)
            return;

        boolean writable=(prop != null && prop.writable()) || (attr_annotation != null && attr_annotation.writable());

        // Is name of @ManagedAttributed or @Property used?
        String attr_name=attr_annotation != null? attr_annotation.name() : prop != null? prop.name() : null;
        if(attr_name != null && !attr_name.trim().isEmpty())
            attr_name=attr_name.trim();
        else {
            // getFooBar() --> foo_bar
            attr_name=Util.methodNameToAttributeName(methodName);
            if(!atts.containsKey(attr_name)) {

                // hmm, maybe we need to look for an attribute fooBar
                String tmp=Util.methodNameToJavaAttributeName(methodName);
                if(atts.containsKey(tmp))
                    attr_name=tmp;
            }
        }

        String descr=attr_annotation != null ? attr_annotation.description() : prop != null? prop.description() : null;
        AttributeEntry attr=atts.get(attr_name);
        if(attr != null) {
            if(isSetMethod(method)) {
                if(attr.setter != null) {
                    if(log.isWarnEnabled())
                        log.warn("setter for \"" + attr_name + "\" is already defined (new method=" + method.getName() + ")");
                }
                else
                    attr.setter=new MethodAccessor(method, instance);
            }
            else {
                if(attr.getter != null) {
                    if(log.isWarnEnabled())
                        log.warn("getter for \"" + attr_name + "\" is already defined (new method=" + method.getName() + ")");
                }
                else
                    attr.getter=new MethodAccessor(method, instance);
            }
        }
        else { // create a new entry in atts
            boolean is_setter=isSetMethod(method);
            String type=is_setter? method.getParameterTypes()[0].getCanonicalName() : method.getReturnType().getCanonicalName();
            MBeanAttributeInfo info=new MBeanAttributeInfo(attr_name, type, descr, true, writable, methodName.startsWith("is"));
            AttributeEntry entry=new AttributeEntry(Util.methodNameToAttributeName(methodName), info);
            if(is_setter)
                entry.setter(new MethodAccessor(method, instance));
            else
                entry.getter(new MethodAccessor(method, instance));
            atts.put(attr_name, entry);
        }
    }



    /** Finds an accessor for an attribute. Tries to find getAttrName(), isAttrName(), attrName() methods. If not
     * found, tries to use reflection to get the value of attr_name. If still not found, creates a NullAccessor. */
    protected static Accessor findGetter(Object target, String attr_name) {
        final String name=Util.attributeNameToMethodName(attr_name);
        Class clazz=target.getClass();
        Method   method=Util.findMethod(target, Arrays.asList("get" + name, "is" + name, toLowerCase(name)));

        if(method != null && (isGetMethod(method) || isIsMethod(method)))
            return new MethodAccessor(method, target);


         // 4. Find a field last_name
        Field field=Util.getField(clazz, attr_name);
        if(field != null)
            return new FieldAccessor(field, target);

        return new NoopAccessor();
    }

    /** Finds an accessor for an attribute. Tries to find setAttrName(), attrName() methods. If not
     * found, tries to use reflection to set the value of attr_name. If still not found, creates a NullAccessor. */
    public static Accessor findSetter(Object target, String attr_name) {
        final String name=Util.attributeNameToMethodName(attr_name);
        final String fluent_name=toLowerCase(name);
        Class clazz=target.getClass();
        Class field_type=null;
        Field field=Util.getField(clazz, attr_name);
        field_type=field != null? field.getType() : null;
        String setter_name="set" + name;

        if(field_type != null) {
            Method method=Util.findMethod(target, Arrays.asList(fluent_name, setter_name), field_type);
            if(method != null && isSetMethod(method))
                return new MethodAccessor(method, target);
        }

        // Find all methods but don't include methods from Object class
        List methods=new ArrayList<>(Arrays.asList(clazz.getMethods()));
        methods.removeAll(OBJECT_METHODS);

        for(Method method: methods) {
            String method_name=method.getName();
            if((method_name.equals(name) || method_name.equals(fluent_name) || method_name.equals(setter_name)) && isSetMethod(method))
                return new MethodAccessor(method, target);
        }

        // Find a field last_name
        if(field != null)
            return new FieldAccessor(field, target);
        return null;
    }

    /** Returns a string with the first letter being lowercase */
    protected static String toLowerCase(String input) {
        if(Character.isUpperCase(input.charAt(0)))
            return input.substring(0, 1).toLowerCase() + input.substring(1);
        return input;
    }


    protected void findFields(Object instance) {
        // traverse class hierarchy and find all annotated fields
        for(Class clazz=instance.getClass(); clazz != null && clazz != Object.class; clazz=clazz.getSuperclass()) {

            Field[] fields=clazz.getDeclaredFields();
            for(Field field: fields) {
                ManagedAttribute attr=field.getAnnotation(ManagedAttribute.class);
                Property prop=field.getAnnotation(Property.class);
                boolean expose_prop=prop != null && prop.exposeAsManagedAttribute();
                boolean expose=attr != null || expose_prop;

                if(expose) {
                    String fieldName=attr != null? attr.name() : (prop != null? prop.name() : null);
                    if(fieldName != null && fieldName.trim().isEmpty())
                        fieldName=field.getName();

                    String descr=attr != null? attr.description() : prop.description();
                    boolean writable=attr != null? attr.writable() : prop.writable();

                    MBeanAttributeInfo info=new MBeanAttributeInfo(fieldName,
                                                                   field.getType().getCanonicalName(),
                                                                   descr,
                                                                   true,
                                                                   !Modifier.isFinal(field.getModifiers()) && writable,
                                                                   false);

                    atts.put(fieldName, new AttributeEntry(field.getName(), info));
                }
            }
        }
    }


    protected Attribute getNamedAttribute(String name) {
        AttributeEntry entry=atts.get(name);
        if(entry != null) {
            try {
                return new Attribute(name, entry.getter.invoke(null));
            }
            catch(Exception e) {
                log.warn(Util.getMessage("AttrReadFailure"), name, e);
            }
        }
        else {
            log.warn(Util.getMessage("MissingAttribute"), name);
        }
        return null;
    }


    protected boolean setNamedAttribute(Attribute attribute) {
        AttributeEntry entry=atts.get(attribute.getName());
        if(entry != null) {
            try {
                entry.setter.invoke(attribute.getValue());
                return true;
            }
            catch(Throwable e) {
                log.warn(Util.getMessage("AttrWriteFailure"), attribute.getName(), e);
            }            
        }
        else {
            log.warn(Util.getMessage("MissingAttribute"), attribute.getName());
        }
        return false;
    }
    
    




    protected static class AttributeEntry {

        /** The name of the field or method. Can be different from the key in atts when name in @Property or
         * @ManagedAttribute  was used */
        protected final String             name;
        protected final MBeanAttributeInfo info;
        protected Accessor                 getter;
        protected Accessor                 setter;

        protected AttributeEntry(String name, MBeanAttributeInfo info) {
            this(name, info, null, null);
        }

        protected AttributeEntry(String name, MBeanAttributeInfo info, Accessor getter, Accessor setter) {
            this.name=name;
            this.info=info;
            this.getter=getter;
            this.setter=setter;
        }

        protected Accessor       getter()                    {return getter;}
        protected AttributeEntry getter(Accessor new_getter) {this.getter=new_getter; return this;}
        protected Accessor       setter()                    {return setter;}
        protected AttributeEntry setter(Accessor new_setter) {this.setter=new_setter; return this;}


        public String toString() {
            StringBuilder sb=new StringBuilder();
            sb.append("AttributeEntry[" + name);
            if(getter != null)
                sb.append(", getter=" + getter);
            if(setter() != null)
                sb.append(", setter=" + setter);
            sb.append("]");
            return sb.toString();
        }
    }


    public interface Accessor {
        /** Invokes a getter or setter. For the getter, new_val must be ignored (null) */
        Object invoke(Object new_val) throws Exception;
    }


    public static class MethodAccessor implements Accessor {
        protected final Method method;
        protected final Object target;

        public MethodAccessor(Method method, Object target) {
            this.method=method;
            this.target=target;
        }

        public Method getMethod() {return method;}

        public Object invoke(Object new_val) throws Exception {
            return new_val != null? method.invoke(target, new_val) : method.invoke(target);
        }

        public String toString() {return "method[" + method.getName() + "()]";}
    }

    public static class FieldAccessor implements Accessor {
        protected final Field  field;
        protected final Object target;

        public FieldAccessor(Field field, Object target) {
            this.field=field;
            this.target=target;
            if(!field.isAccessible())
                field.setAccessible(true);
        }

        public Field getField() {return field;}

        public Object invoke(Object new_val) throws Exception {
            if(new_val == null)
                return field.get(target);
            else {
                field.set(target, new_val);
                return null;
            }
        }

        public String toString() {return "field[" + field.getName() + "]";}
    }


    public static class NoopAccessor implements Accessor {
        public Object invoke(Object new_val) throws Exception {return null;}

        public String toString() {return "NoopAccessor";}
    }


}