Merge "BUG-2511 Fix XXE vulnerability in Netconf"
[controller.git] / opendaylight / adsal / northbound / commons / src / main / java / org / opendaylight / controller / northbound / commons / query / TypeInfo.java
1 package org.opendaylight.controller.northbound.commons.query;
2
3 import java.beans.BeanInfo;
4 import java.beans.IntrospectionException;
5 import java.beans.Introspector;
6 import java.beans.PropertyDescriptor;
7 import java.lang.annotation.Annotation;
8 import java.lang.reflect.Field;
9 import java.lang.reflect.Method;
10 import java.lang.reflect.Modifier;
11 import java.lang.reflect.ParameterizedType;
12 import java.lang.reflect.Type;
13 import java.util.Collection;
14 import java.util.HashMap;
15 import java.util.Map;
16
17 import javax.xml.bind.annotation.XmlAccessType;
18 import javax.xml.bind.annotation.XmlAccessorType;
19 import javax.xml.bind.annotation.XmlAttribute;
20 import javax.xml.bind.annotation.XmlElement;
21 import javax.xml.bind.annotation.XmlElementRef;
22 import javax.xml.bind.annotation.XmlElementWrapper;
23 import javax.xml.bind.annotation.XmlRootElement;
24 import javax.xml.bind.annotation.XmlTransient;
25 import javax.xml.bind.annotation.XmlType;
26
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 /**
31  * A wrapper over a JAXB type to allow traversal of the object graph and
32  * search for specific values in the object tree.
33  */
34 /*package*/ class TypeInfo {
35
36     public static final Logger LOGGER = LoggerFactory.getLogger(TypeInfo.class);
37     public static final String DEFAULT_NAME = "##default";
38
39     protected final String _name; // the jaxb name
40     protected Class<?> _class; // jaxb type class
41     protected final XmlAccessType _accessType; // jaxb access type
42     protected final Accessor _accessor; // accessor to access object value
43     protected Map<String,TypeInfo> _types = new HashMap<String,TypeInfo>();
44     protected volatile boolean _explored = false;
45     /**
46      * Create a TypeInfo with a name and a class type. The accessor will be null
47      * for a root node.
48      */
49     protected TypeInfo(String name, Class<?> clz, Accessor accessor) {
50         _name = name;
51         _class = clz;
52         _accessor = accessor;
53         XmlAccessorType accessorType = null;
54         if(clz == null) {
55             throw new NullPointerException("Type class can not be null");
56         }
57         accessorType = clz.getAnnotation(XmlAccessorType.class);
58         _accessType = (accessorType == null ?
59                 XmlAccessType.PUBLIC_MEMBER : accessorType.value());
60         if (LOGGER.isDebugEnabled()) {
61             LOGGER.debug("Created type info name:{} type:{}", _name, _class);
62         }
63     }
64
65     /**
66      * @return the Accessor to access the value
67      */
68     public final Accessor getAccessor() {
69         return _accessor;
70     }
71
72     /**
73      * @return get the child by name
74      */
75     public final TypeInfo getChild(String name) {
76         return _types.get(name);
77     }
78
79     public TypeInfo getCollectionChild(Class<?> childType) {
80         explore();
81         for (TypeInfo ti : _types.values()) {
82             if (Collection.class.isAssignableFrom(ti.getType())) {
83                 ParameterizedType p = (ParameterizedType)
84                         ti.getAccessor().getGenericType();
85                 Type[] pts = p.getActualTypeArguments();
86                 if (pts.length == 1 && pts[0].equals(childType)) {
87                     return ti;
88                 }
89             }
90         }
91         return null;
92     }
93
94     public Class getType() {
95         return _class;
96     }
97
98     public String getName() {
99         return _name;
100     }
101
102     /**
103      * @return the object value by a selector query
104      */
105     public Object retrieve(Object target, String[] query, int index)
106             throws QueryException {
107         if (LOGGER.isDebugEnabled()) {
108             LOGGER.debug("retrieve: {}/{} type:{}", index, query.length, target.getClass());
109         }
110         if (index >= query.length) {
111             return null;
112         }
113         explore();
114         if (!target.getClass().equals(_class)) {
115             if (_class.isAssignableFrom(target.getClass())) {
116                 if (LOGGER.isDebugEnabled()) {
117                     LOGGER.debug("Handling subtype {} of {} ", target.getClass(), _class);
118                 }
119                 // explore the subtype
120                 TypeInfo subTypeInfo = new TypeInfo(getRootName(target.getClass()),
121                         target.getClass(), _accessor);
122                 return subTypeInfo.retrieve(target, query, index);
123             } else {
124                 // non compatible object; bail out
125                 return null;
126             }
127         }
128         TypeInfo child = getChild(query[index]);
129         if (child == null) {
130             return null;
131         }
132         target = child.getAccessor().getValue(target);
133         if (index+1 == query.length) {
134             // match found
135             return target;
136         }
137         return child.retrieve(target, query, index+1);
138     }
139
140     /**
141      * Explore the type info for children.
142      */
143     public synchronized void explore() {
144         if (_explored) {
145             return;
146         }
147         for (Class<?> c = _class; c != null; c = c.getSuperclass()) {
148             if (c.equals(Object.class)) {
149                 break;
150             }
151             // Currently only fields and methods annotated with JAXB annotations are
152             // considered as valid for search purposes.
153             //check methods first
154             for (Method m : c.getDeclaredMethods()) {
155                 String tn = getTypeName(m, _accessType);
156                 if (tn != null) {
157                     if (LOGGER.isDebugEnabled()) {
158                         LOGGER.debug(
159                             "exploring type: {} name: {} method: {}",
160                             _class.getSimpleName(), tn, m);
161                     }
162                     _types.put(tn, createTypeInfo(tn, new Accessor(m)));
163                 }
164             }
165             for (Field f : c.getDeclaredFields()) {
166                 String tn = getTypeName(f, _accessType);
167                 if (tn != null) {
168                     if (LOGGER.isDebugEnabled()) {
169                         LOGGER.debug(
170                             "exploring type: {} name: {} field: {}",
171                             _class.getSimpleName(), tn, f);
172                     }
173                     _types.put(tn, createTypeInfo(tn, new Accessor(f)));
174                 }
175             }
176         }
177         _explored = true;
178     }
179
180     public static final String getTypeName(Field f, XmlAccessType access) {
181         // ignore static, transient and xmltransient fields
182         if (Modifier.isTransient(f.getModifiers()) ||
183                 Modifier.isStatic(f.getModifiers()) ||
184                 f.getAnnotation(XmlTransient.class) != null ) {
185             return null;
186         }
187         // try to read annotation
188         String name = getTypeName(f.getAnnotations(), f.getName());
189         if (name != null) return name;
190         // no annotation present check accesstype
191         else if (access == XmlAccessType.NONE) { // none return name
192             return name;
193         } else if (access == XmlAccessType.FIELD) {
194             // return field name if no annotation present
195             return f.getName();
196         } else if (access == XmlAccessType.PUBLIC_MEMBER
197                 && Modifier.isPublic(f.getModifiers())) { // look for public access
198             return f.getName();
199         }
200         // return annotated name ( if any )
201         return null;
202     }
203
204     public static final String getTypeName(Method m, XmlAccessType access) {
205         // ignore static, transient and xmltransient fields
206         if (Modifier.isStatic(m.getModifiers()) ||
207                 m.getAnnotation(XmlTransient.class) != null ) {
208             return null;
209         }
210         // try to read annotation
211         String name = getTypeName(m.getAnnotations(), m.getName());
212         if (name != null) return name;
213         //check acces type
214         else if (access == XmlAccessType.NONE) { // none return name
215             return name;
216         } else if (access == XmlAccessType.PROPERTY) {
217             // return bean property name if no annotation present
218             return getBeanPropertyName(m);
219         } else if (access == XmlAccessType.PUBLIC_MEMBER
220                 && Modifier.isPublic(m.getModifiers())) { // look for public access
221             return getBeanPropertyName(m);
222         }
223         return null;
224     }
225
226     private static String getBeanPropertyName(Method m){
227         try
228         {
229             Class<?> clazz=m.getDeclaringClass();
230             BeanInfo info = Introspector.getBeanInfo(clazz);
231             PropertyDescriptor[] props = info.getPropertyDescriptors();
232             for (PropertyDescriptor pd : props)
233             {
234                 if (m.equals(pd.getReadMethod())) {
235                     return pd.getName();
236                 }
237             }
238         }
239         catch (IntrospectionException e)
240         {
241             LOGGER.error("Could not read bean property name for method = {}",
242                     m.getName(), e);
243         }
244         return null;
245     }
246
247     public static TypeInfo createRoot(String name, Class<?> clz) {
248         // root is always a composite type
249         // FIXME assert its a JAXB type
250         XmlRootElement root = clz.getAnnotation(XmlRootElement.class);
251         if (root == null) {
252             throw new IllegalArgumentException("Not a JAXB type: " + clz);
253         }
254         if (name == null) {
255             name = getRootName(clz);
256         }
257         return new TypeInfo(name, clz, null);
258     }
259
260     public static TypeInfo createTypeInfo(String name, Accessor accessor) {
261         if (accessor.getAccessibleObject().getAnnotation(XmlElementWrapper.class) != null) {
262             //XmlElementWrapperType
263             return new WrapperTypeInfo(name, accessor);
264         } else if (Collection.class.isAssignableFrom(accessor.getType())) {
265             // collection type
266             return new IteratableTypeInfo(name, accessor);
267         }
268         return new TypeInfo(name, accessor.getType(), accessor);
269     }
270
271     public static String getRootName(Class<?> cls) {
272         XmlRootElement root = cls.getAnnotation(XmlRootElement.class);
273         if (root == null) {
274             return null;
275         }
276         String rootName = root.name();
277         if (DEFAULT_NAME.equals(rootName)) {
278             String clsName = cls.getSimpleName();
279             rootName = Character.toLowerCase(clsName.charAt(0)) + clsName.substring(1);
280         }
281         return rootName;
282     }
283
284     protected static String getTypeName(Annotation[] annotations, String dflt) {
285         String name = null;
286         for (Annotation a : annotations) {
287             if (a.annotationType() == XmlAttribute.class) {
288                 name = ((XmlAttribute)a).name();
289             } else if (a.annotationType() == XmlElement.class) {
290                 name = ((XmlElement)a).name();
291             } else if (a.annotationType() == XmlElementRef.class) {
292                 name = ((XmlElementRef)a).name();
293             } else if (a.annotationType() == XmlElementWrapper.class) {
294                 name = ((XmlElementWrapper)a).name();
295                 // break the loop as we don't want name to be overwritten by XmlElement
296                 break;
297             } else if (a.annotationType() == XmlType.class) {
298                 name = ((XmlType)a).name();
299             } else if (a.annotationType() == XmlTransient.class) {
300                 // transient type
301                 return null;
302             }
303         }
304         if (DEFAULT_NAME.equals(name)) {
305             return dflt;
306         }
307         return name;
308     }
309
310     @Override
311     public String toString() {
312         return " TypeInfo [_name=" + _name + ", _class=" + _class
313                 + ", _accessType=" + _accessType + ", _accessor=" + _accessor
314                 + ", _types=" + _types + ", _explored=" + _explored + " ] ";
315     }
316 }