DCAE-D be initial commit
[sdc/dcae-d/dt-be-main.git] / dcaedt_catalog / commons / src / main / java / org / onap / sdc / dcae / catalog / commons / Proxy.java
1 package org.onap.sdc.dcae.catalog.commons;
2
3 import java.util.List;
4 import java.util.LinkedList;
5 import java.util.Map;
6 import java.util.Collections;
7
8 import java.util.stream.Collectors;
9
10 import java.lang.annotation.ElementType;
11 import java.lang.annotation.Retention;
12 import java.lang.annotation.RetentionPolicy;
13 import java.lang.annotation.Target;
14
15 import java.lang.reflect.Type;
16 import java.lang.reflect.Method;
17 import java.lang.reflect.Array;
18 import java.lang.reflect.Constructor;
19 import java.lang.reflect.ParameterizedType;
20 import java.lang.reflect.InvocationHandler;
21 import java.lang.reflect.InvocationTargetException;
22
23 import java.lang.invoke.MethodHandles;
24
25 import com.google.common.reflect.Invokable;
26 import org.onap.sdc.dcae.catalog.commons.Proxy;
27 import org.onap.sdc.dcae.catalog.commons.ProxyBuilder;
28 import com.google.common.reflect.AbstractInvocationHandler;
29
30 import org.apache.commons.beanutils.ConvertUtils;
31
32 import org.json.JSONObject;
33 import org.json.JSONArray;
34
35 public class Proxy extends AbstractInvocationHandler {
36
37     @Retention(RetentionPolicy.RUNTIME)
38     @Target(ElementType.METHOD)
39
40     public static @interface DataMap {
41
42         public String map() default "";
43
44         public boolean proxy() default false;
45
46         public Class elementType() default Void.class;
47     }
48
49
50     public static final Constructor<MethodHandles.Lookup> lookupHandleConstructor;
51
52     static {
53         try {
54             lookupHandleConstructor =
55                     MethodHandles.Lookup.class.getDeclaredConstructor(Class.class,
56                                                                                                                         int.class);
57
58             if (!lookupHandleConstructor.isAccessible()) {
59             lookupHandleConstructor.setAccessible(true);
60             }
61         }
62         catch (Exception x) {
63             throw new RuntimeException(x);
64         }
65     }
66
67
68     private JSONObject          data;
69     private ProxyBuilder        builder;
70
71     protected Proxy(JSONObject theData, ProxyBuilder theBuilder) {
72         this.data = theData;
73         this.builder = theBuilder;
74     }
75
76     public JSONObject data() {
77         return this.data;
78     }
79
80     public ProxyBuilder getBuilder() {
81         return this.builder;
82     }
83
84     protected Object handleInvocation(
85                                             Object theProxy,Method theMethod,Object[] theArgs)
86                                                                                                                 throws Throwable {
87         if (theMethod.isDefault()) {
88                 final Class<?> declaringClass = theMethod.getDeclaringClass();
89
90             return lookupHandleConstructor
91                             .newInstance(declaringClass, MethodHandles.Lookup.PRIVATE)
92                             .unreflectSpecial(theMethod, declaringClass)
93                             .bindTo(theProxy)
94                             .invokeWithArguments(theArgs);
95         }
96
97         String key = theMethod.getName();
98
99         Proxy.DataMap dataMap = (Proxy.DataMap)theMethod.getAnnotation(Proxy.DataMap.class);
100         if (dataMap != null) {
101             String dataKey = dataMap.map();
102             if (dataKey != null && !"".equals(dataKey))
103                 key = dataKey;
104         }
105
106         //this is ugly, can this be done through an extension mechanism such as plugging in functions?
107         if ( builder.hasExtension(key) )
108             return this.builder.extension(key).apply(this, theArgs);
109
110         //we give priority to the context (because of the 'catalog' property issue in catalog service) but
111         //how natural is this?
112         Object val = this.builder.context(key);
113         if (val == null)
114             val = this.data.opt(key);
115
116         if (val == null)
117             return null;
118
119 //as we create proxies here we should store them back in the 'data' so that we do not do it again
120 //can we always 'recognize' them?
121         if (val instanceof String &&
122                 String.class != theMethod.getReturnType()) {
123             //??This will yield a POJO ..
124             return ConvertUtils.convert((String)val, theMethod.getReturnType());
125         }
126         else if (val instanceof JSONObject) {
127             if (dataMap != null && dataMap.proxy()) {
128                 return builder.build((JSONObject)val, theMethod.getReturnType());
129             }
130         }
131         else if (val instanceof JSONArray&& dataMap != null &&
132                  dataMap.proxy() &&
133                  List.class.isAssignableFrom(theMethod.getReturnType())) {
134
135             List res = (List) theMethod.getReturnType().newInstance();
136             for (int i = 0; i < ((JSONArray) val).length(); i++) {
137                 res.add(builder.build(((JSONArray) val).getJSONObject(i), dataMap.elementType()));
138             }
139             return res;
140
141         }
142         return val;
143     }
144 }