package org.opendaylight.controller.northbound.commons.query; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Collection; import java.util.HashMap; import java.util.Map; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementRef; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.XmlType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A wrapper over a JAXB type to allow traversal of the object graph and * search for specific values in the object tree. */ /*package*/ class TypeInfo { public static final Logger LOGGER = LoggerFactory.getLogger(TypeInfo.class); public static final String DEFAULT_NAME = "##default"; protected final String _name; // the jaxb name protected Class _class; // jaxb type class protected final XmlAccessType _accessType; // jaxb access type protected final Accessor _accessor; // accessor to access object value protected Map _types = new HashMap(); protected volatile boolean _explored = false; /** * Create a TypeInfo with a name and a class type. The accessor will be null * for a root node. */ protected TypeInfo(String name, Class clz, Accessor accessor) { _name = name; _class = clz; _accessor = accessor; XmlAccessorType accessorType = null; if(clz == null) { throw new NullPointerException("Type class can not be null"); } accessorType = clz.getAnnotation(XmlAccessorType.class); _accessType = (accessorType == null ? XmlAccessType.PUBLIC_MEMBER : accessorType.value()); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Created type info name:{} type:{}", _name, _class); } } /** * @return the Accessor to access the value */ public final Accessor getAccessor() { return _accessor; } /** * @return get the child by name */ public final TypeInfo getChild(String name) { return _types.get(name); } public TypeInfo getCollectionChild(Class childType) { explore(); for (TypeInfo ti : _types.values()) { if (Collection.class.isAssignableFrom(ti.getType())) { ParameterizedType p = (ParameterizedType) ti.getAccessor().getGenericType(); Type[] pts = p.getActualTypeArguments(); if (pts.length == 1 && pts[0].equals(childType)) { return ti; } } } return null; } public Class getType() { return _class; } public String getName() { return _name; } /** * @return the object value by a selector query */ public Object retrieve(Object target, String[] query, int index) throws QueryException { if (LOGGER.isDebugEnabled()) { LOGGER.debug("retrieve: {}/{} type:{}", index, query.length, target.getClass()); } if (index >= query.length) { return null; } explore(); if (!target.getClass().equals(_class)) { if (_class.isAssignableFrom(target.getClass())) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Handling subtype {} of {} ", target.getClass(), _class); } // explore the subtype TypeInfo subTypeInfo = new TypeInfo(getRootName(target.getClass()), target.getClass(), _accessor); return subTypeInfo.retrieve(target, query, index); } else { // non compatible object; bail out return null; } } TypeInfo child = getChild(query[index]); if (child == null) { return null; } target = child.getAccessor().getValue(target); if (index+1 == query.length) { // match found return target; } return child.retrieve(target, query, index+1); } /** * Explore the type info for children. */ public synchronized void explore() { if (_explored) { return; } for (Class c = _class; c != null; c = c.getSuperclass()) { if (c.equals(Object.class)) { break; } // Currently only fields and methods annotated with JAXB annotations are // considered as valid for search purposes. //check methods first for (Method m : c.getDeclaredMethods()) { String tn = getTypeName(m, _accessType); if (tn != null) { if (LOGGER.isDebugEnabled()) { LOGGER.debug( "exploring type: {} name: {} method: {}", _class.getSimpleName(), tn, m); } _types.put(tn, createTypeInfo(tn, new Accessor(m))); } } for (Field f : c.getDeclaredFields()) { String tn = getTypeName(f, _accessType); if (tn != null) { if (LOGGER.isDebugEnabled()) { LOGGER.debug( "exploring type: {} name: {} field: {}", _class.getSimpleName(), tn, f); } _types.put(tn, createTypeInfo(tn, new Accessor(f))); } } } _explored = true; } public static final String getTypeName(Field f, XmlAccessType access) { // ignore static, transient and xmltransient fields if (Modifier.isTransient(f.getModifiers()) || Modifier.isStatic(f.getModifiers()) || f.getAnnotation(XmlTransient.class) != null ) { return null; } // try to read annotation String name = getTypeName(f.getAnnotations(), f.getName()); if (name != null) return name; // no annotation present check accesstype else if (access == XmlAccessType.NONE) { // none return name return name; } else if (access == XmlAccessType.FIELD) { // return field name if no annotation present return f.getName(); } else if (access == XmlAccessType.PUBLIC_MEMBER && Modifier.isPublic(f.getModifiers())) { // look for public access return f.getName(); } // return annotated name ( if any ) return null; } public static final String getTypeName(Method m, XmlAccessType access) { // ignore static, transient and xmltransient fields if (Modifier.isStatic(m.getModifiers()) || m.getAnnotation(XmlTransient.class) != null ) { return null; } // try to read annotation String name = getTypeName(m.getAnnotations(), m.getName()); if (name != null) return name; //check acces type else if (access == XmlAccessType.NONE) { // none return name return name; } else if (access == XmlAccessType.PROPERTY) { // return bean property name if no annotation present return getBeanPropertyName(m); } else if (access == XmlAccessType.PUBLIC_MEMBER && Modifier.isPublic(m.getModifiers())) { // look for public access return getBeanPropertyName(m); } return null; } private static String getBeanPropertyName(Method m){ try { Class clazz=m.getDeclaringClass(); BeanInfo info = Introspector.getBeanInfo(clazz); PropertyDescriptor[] props = info.getPropertyDescriptors(); for (PropertyDescriptor pd : props) { if (m.equals(pd.getReadMethod())) { return pd.getName(); } } } catch (IntrospectionException e) { LOGGER.error("Could not read bean property name for method = {}", m.getName(), e); } return null; } public static TypeInfo createRoot(String name, Class clz) { // root is always a composite type // FIXME assert its a JAXB type XmlRootElement root = clz.getAnnotation(XmlRootElement.class); if (root == null) { throw new IllegalArgumentException("Not a JAXB type: " + clz); } if (name == null) { name = getRootName(clz); } return new TypeInfo(name, clz, null); } public static TypeInfo createTypeInfo(String name, Accessor accessor) { if (accessor.getAccessibleObject().getAnnotation(XmlElementWrapper.class) != null) { //XmlElementWrapperType return new WrapperTypeInfo(name, accessor); } else if (Collection.class.isAssignableFrom(accessor.getType())) { // collection type return new IteratableTypeInfo(name, accessor); } return new TypeInfo(name, accessor.getType(), accessor); } public static String getRootName(Class cls) { XmlRootElement root = cls.getAnnotation(XmlRootElement.class); if (root == null) { return null; } String rootName = root.name(); if (DEFAULT_NAME.equals(rootName)) { String clsName = cls.getSimpleName(); rootName = Character.toLowerCase(clsName.charAt(0)) + clsName.substring(1); } return rootName; } protected static String getTypeName(Annotation[] annotations, String dflt) { String name = null; for (Annotation a : annotations) { if (a.annotationType() == XmlAttribute.class) { name = ((XmlAttribute)a).name(); } else if (a.annotationType() == XmlElement.class) { name = ((XmlElement)a).name(); } else if (a.annotationType() == XmlElementRef.class) { name = ((XmlElementRef)a).name(); } else if (a.annotationType() == XmlElementWrapper.class) { name = ((XmlElementWrapper)a).name(); // break the loop as we don't want name to be overwritten by XmlElement break; } else if (a.annotationType() == XmlType.class) { name = ((XmlType)a).name(); } else if (a.annotationType() == XmlTransient.class) { // transient type return null; } } if (DEFAULT_NAME.equals(name)) { return dflt; } return name; } @Override public String toString() { return " TypeInfo [_name=" + _name + ", _class=" + _class + ", _accessType=" + _accessType + ", _accessor=" + _accessor + ", _types=" + _types + ", _explored=" + _explored + " ] "; } }