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

org.gridkit.lab.util.jmx.mxstruct.MXStruct Maven / Gradle / Ivy

package org.gridkit.lab.util.jmx.mxstruct;

import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.openmbean.CompositeData;

public abstract class MXStruct implements Cloneable {

	public static ObjectName name(String name) {
		try {
			return new ObjectName(name);
		} catch (MalformedObjectNameException e) {
			throw new RuntimeException(e);
		}
	}
	
	private Map meta;
	private String[] attrNames;
	private Map data;
	private long timestamp = System.nanoTime();
	
	protected MXStruct() {
		meta = collectMeta();
		attrNames = new String[meta.size()];
		int n = 0;
		for(AttrInfo ai: meta.values()) {
			attrNames[n++] = ai.attrName;
		}
	}

	private Map collectMeta() {
		
		Map meta = new LinkedHashMap();
		
		for(Method m : getClass().getMethods()) {
			if (m.isAnnotationPresent(AttrName.class)) {
				AttrName an = m.getAnnotation(AttrName.class);
				AsObject ot = m.getAnnotation(AsObject.class);
				AsCollection ct = m.getAnnotation(AsCollection.class);
				AsMap mt = m.getAnnotation(AsMap.class);
				int ac = (ot == null ? 0 : 1)
						+(ct == null ? 0 : 1)
						+(mt == null ? 0 : 1);
				
				if (ac > 1) {
					throw new IllegalArgumentException("You can specify only one \"As\" annotation for method");
				}
				AttrInfo ai = new AttrInfo();
				ai.methodName = m.getName();
				ai.attrName = an.value();
				
				if (ot != null) {
					Class t = ot.value();
					ai.converter = createConverter(t);
				}
				else if (ct != null) {
					Class t = ct.value();
					ai.converter = new CollectionConverter(createConverter(t));
				}
				else if (mt != null) {
					String key = mt.key();
					String val = mt.val();
					Class t =mt.type();
					ai.converter = new MapConverter(key, val, createConverter(t));
				}
				else if (MXStruct.class.isAssignableFrom(m.getReturnType())) {
					Class t = m.getReturnType();
					ai.converter = createConverter(t);					
				}
				meta.put(ai.methodName, ai);
			}
		}
				
		return meta;
	}

	private Converter createConverter(Class t) {
		if (MXStruct.class.isAssignableFrom(t)) {
			return new StructConverter(t);
		}
		else if (t == Void.class) {
			return null;
		}
		else {
			throw new IllegalArgumentException("Class " + t.getName() + " is not an " + MXStruct.class.getSimpleName());
		}
	}

	/**
	 * @return value of {@link System#nanoTime()} at the moment of creation of MXStruct
	 */
	public long getMXStructTimestamp() {
		return timestamp;
	}
	
	@SuppressWarnings("unchecked")
	public  V read(MBeanServerConnection conn, ObjectName name) throws ReflectionException, IOException {
		MXStruct that;
		that = (MXStruct) clone();
		that.meta = meta;
		that.attrNames = attrNames;
		that.data = new LinkedHashMap();
		try {
			AttributeList al = conn.getAttributes(name, attrNames);
			that.timestamp = System.nanoTime();
			for (Attribute attr: al.asList()) {
				that.data.put(attr.getName(), attr.getValue());
			}
			return (V) that;
		}
		catch(InstanceNotFoundException e) {
			return null;
		}			
	}
	
	@SuppressWarnings("unchecked")
	public  V read(CompositeData cdata) {
		MXStruct that;
		that = (MXStruct) clone();
		that.timestamp = System.nanoTime();
		that.meta = meta;
		that.attrNames = attrNames;
		that.data = new LinkedHashMap();
		for(String attr : attrNames) {
			that.data.put(attr, cdata.get(attr));
		}
		return (V)that;
	}
	
	protected  V getMXAttr() {
		return getMXAttrInternal();
	}

	protected int getInt() {
		return (Integer)getMXAttrInternal();
	}

	protected long getLong() {
		return (Long)getMXAttrInternal();
	}

	protected boolean getBoolean() {
		return (Boolean)getMXAttrInternal();
	}

	protected float getFloat() {
		return (Float)getMXAttrInternal();
	}

	protected double getDouble() {
		return (Double)getMXAttrInternal();
	}
	
	@SuppressWarnings("unchecked")
	private  V getMXAttrInternal() {
		StackTraceElement[] trace = Thread.currentThread().getStackTrace();
		String methodName = trace[3].getMethodName();
		AttrInfo info = meta.get(methodName);
		if (info == null) {
			throw new IllegalArgumentException("Method " + methodName + " is not annotated with MBean meta data");
		}
		Object val = data.get(info.attrName);
		if (val != null && info.converter != null) {
			return (V) info.converter.convert(val);
		}
		else {
			return (V) val;
		}
	}
	
	@Override
	protected Object clone() {
		try {
			return super.clone();
		} catch (CloneNotSupportedException e) {
			throw new Error("It is cloneable");
		}
	}
	
	private class AttrInfo {
		
		private String methodName;
		private String attrName;
		private Converter converter;
		
	}
	
	private interface Converter {
		
		public Object convert(Object val);
		
	}	
	
	private static class StructConverter implements Converter {
		
		private MXStruct proto;

		public StructConverter(Class type) {
			try {
				this.proto = (MXStruct) type.newInstance();
			} catch (InstantiationException e) {
				throw new IllegalArgumentException("Cannot instantiate", e);
			} catch (IllegalAccessException e) {
				throw new IllegalArgumentException("Cannot instantiate", e);
			}
		}

		@Override
		public Object convert(Object val) {
			return val == null ? null : proto.read((CompositeData) val);
		}
	}
	
	private static class CollectionConverter implements Converter {
		
		private Converter elementConverter;

		public CollectionConverter(Converter elementConverter) {
			this.elementConverter = elementConverter;
		}

		@Override
		public Object convert(Object val) {
			if (val == null) {
				return null;
			}
			else {
				List x = new ArrayList();
				for(Object o : iterate(val)) {
					x.add(elementConverter == null ? o : elementConverter.convert(o));
				}
				return x;
			}
		}
	}

	private static class MapConverter implements Converter {
		
		private String keyAttr;
		private String valueAttr;
		private Converter elementConverter;
		
		public MapConverter(String keyAttr, String valueAttr, Converter elementConverter) {
			this.keyAttr = normString(keyAttr);
			this.valueAttr = normString(valueAttr);
			this.elementConverter = elementConverter;
		}

		private String normString(String valueAttr) {
			valueAttr = valueAttr.trim();
			return (valueAttr != null && valueAttr.length() > 1) ? valueAttr : null;
		}

		@Override
		public Object convert(Object val) {
			if (val == null) {
				return null;
			}
			else {
				Map x = new LinkedHashMap();
				for(Object o : iterate(val)) {
					CompositeData cdata = (CompositeData) o;
					Object key = cdata.get(keyAttr);
					Object v = valueAttr == null ? cdata : cdata.get(valueAttr);
					if (elementConverter != null) {
						v = elementConverter.convert(v);
					}
					x.put(key, v);
				}
				return x;
			}
		}
	}
	
	private static Iterable iterate(Object val) {
		if (val instanceof Object[]) {
			return Arrays.asList((Object[])val);
		}
		else if (val instanceof Collection) {
			return (Collection)val;
		}
		else if (val instanceof Map) {
			return ((Map)val).values();
		}
		else if (val == null) {
			return Collections.emptyList();
		}
		else {
			throw new IllegalArgumentException("Cannot iterate: " + val);
		}
	}

	@Override
	public String toString() {
		return getClass().getSimpleName() + ":" + data.toString();
	}
	
	@Retention(RetentionPolicy.RUNTIME)
	protected @interface AttrName {
		String value();
	}
	
	@Retention(RetentionPolicy.RUNTIME)
	protected @interface AsObject {
		Class value();
	}
	
	@Retention(RetentionPolicy.RUNTIME)
	protected @interface AsCollection {
		Class value() default Void.class;
	}
	
	@Retention(RetentionPolicy.RUNTIME)
	protected @interface AsMap {
		String key() default "key";
		String val() default "";
		Class type() default Void.class;
	}
}