11e6233fcbd3a36101e49657d3853980999e473c
[aai/aai-common.git] / aai-core / src / main / java / org / onap / aai / introspection / Introspector.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *    http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.aai.introspection;
22
23 import com.att.eelf.configuration.EELFLogger;
24 import com.att.eelf.configuration.EELFManager;
25 import com.google.common.base.CaseFormat;
26
27 import java.io.UnsupportedEncodingException;
28 import java.lang.reflect.InvocationTargetException;
29 import java.util.*;
30 import java.util.stream.Collectors;
31
32 import org.apache.commons.lang.ClassUtils;
33 import org.eclipse.persistence.exceptions.DynamicException;
34 import org.onap.aai.config.SpringContextAware;
35 import org.onap.aai.introspection.exceptions.AAIUnknownObjectException;
36 import org.onap.aai.logging.ErrorLogHelper;
37 import org.onap.aai.logging.LogFormatTools;
38 import org.onap.aai.nodes.CaseFormatStore;
39 import org.onap.aai.nodes.NodeIngestor;
40 import org.onap.aai.restcore.MediaType;
41 import org.onap.aai.schema.enums.ObjectMetadata;
42 import org.onap.aai.schema.enums.PropertyMetadata;
43 import org.onap.aai.setup.SchemaVersion;
44 import org.onap.aai.workarounds.NamingExceptions;
45
46 public abstract class Introspector implements Cloneable {
47
48     private static final EELFLogger LOGGER = EELFManager.getInstance().getLogger(Introspector.class);
49
50     protected String className;
51     protected String uriChain = "";
52     protected Loader loader;
53     protected final NamingExceptions namingException = NamingExceptions.getInstance();
54     private Set<String> uniqueProperties = null;
55     private Set<String> indexedProperties = null;
56     private Set<String> allKeys = null;
57
58     protected CaseFormatStore caseFormatStore = null;
59     protected NodeIngestor nodeIngestor;
60
61     protected Introspector(Object obj) {
62         this.nodeIngestor = SpringContextAware.getBean(NodeIngestor.class);
63         this.caseFormatStore = nodeIngestor.getCaseFormatStore();
64     }
65
66     public abstract boolean hasProperty(String name);
67
68     protected String convertPropertyName(String name) {
69         return caseFormatStore.fromLowerHyphenToLowerCamel(name).orElseGet(() -> {
70             LOGGER.debug("Unable to find {} in the store from lower hyphen to lower camel", name);
71             return CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL, name);
72         });
73     }
74
75     protected abstract Object get(String name);
76
77     protected abstract void set(String name, Object value);
78
79     /**
80      *
81      * @param name the property name you'd like to retrieve the value for
82      * @return the value of the property
83      */
84     public <T> T getValue(String name) {
85         String convertedName = convertPropertyName(name);
86         Object result = null;
87
88         if (this.hasProperty(name)) {
89             result = this.get(convertedName);
90         } else {
91             /* property not found - slightly ambiguous */
92             return null;
93         }
94
95         Class<?> clazz = this.getClass(name);
96         if (this.isListType(name) && result == null) {
97             try {
98                 this.set(convertedName, clazz.newInstance());
99                 result = this.get(convertedName);
100             } catch (DynamicException | InstantiationException | IllegalAccessException e) {
101                 LOGGER.warn(e.getMessage(), e);
102             }
103         }
104
105         return (T) result;
106     }
107
108     public Introspector getWrappedValue(String name) {
109         String convertedName = convertPropertyName(name);
110         Object value = null;
111
112         if (this.hasProperty(name)) {
113             value = this.get(convertedName);
114         } else {
115             /* property not found - slightly ambiguous */
116             return null;
117         }
118
119         Class<?> clazz = this.getClass(name);
120         if (this.isListType(name) && value == null) {
121             try {
122                 this.set(convertedName, clazz.newInstance());
123                 value = this.get(convertedName);
124             } catch (DynamicException | InstantiationException | IllegalAccessException e) {
125                 LOGGER.warn(e.getMessage(), e);
126             }
127         }
128         if (value != null) {
129             return IntrospectorFactory.newInstance(this.getModelType(), value);
130         } else {
131             // no value
132             return null;
133         }
134
135     }
136
137     public List<Introspector> getWrappedListValue(String name) {
138         String convertedName = convertPropertyName(name);
139         Object value = null;
140         List<Introspector> resultList = new ArrayList<>();
141         if (this.hasProperty(name)) {
142             value = this.get(convertedName);
143         } else {
144             /* property not found - slightly ambiguous */
145             return null;
146         }
147         boolean isListType = this.isListType(name);
148         if (!this.isListType(name)) {
149             return null;
150         }
151         Class<?> clazz = this.getClass(name);
152         if (isListType && value == null) {
153             try {
154                 this.set(convertedName, clazz.newInstance());
155                 value = this.get(convertedName);
156             } catch (DynamicException | InstantiationException | IllegalAccessException e) {
157                 LOGGER.warn(e.getMessage(), e);
158             }
159         }
160
161         List<Object> valueList = (List<Object>) value;
162
163         for (Object item : valueList) {
164             resultList.add(IntrospectorFactory.newInstance(this.getModelType(), item));
165         }
166
167         return resultList;
168
169     }
170
171     public Object castValueAccordingToSchema(String name, Object obj) {
172         Object result = obj;
173         Class<?> nameClass = this.getClass(name);
174         if (nameClass == null) {
175             throw new IllegalArgumentException("property: " + name + " does not exist on " + this.getDbName());
176         }
177         if (obj != null) {
178
179             try {
180                 if (!nameClass.isAssignableFrom(obj.getClass())) {
181                     if (nameClass.isPrimitive()) {
182                         nameClass = ClassUtils.primitiveToWrapper(nameClass);
183                         result = nameClass.getConstructor(String.class).newInstance(obj.toString());
184                     }
185                     if (obj instanceof String) {
186                         result = nameClass.getConstructor(String.class).newInstance(obj);
187                     } else if (!this.isListType(name) && !this.isComplexType(name)) {
188                         // box = obj.toString();
189                         result = nameClass.getConstructor(String.class).newInstance(obj.toString());
190                     }
191                 }
192             } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
193                     | InvocationTargetException | NoSuchMethodException | SecurityException e) {
194                 ErrorLogHelper.logError("AAI_4017", e.getMessage());
195             }
196         }
197         return result;
198     }
199
200     public List<Object> castValueAccordingToSchema(String name, List<?> objs) {
201         List<Object> result = new ArrayList<>();
202
203         for (Object item : objs) {
204             result.add(this.castValueAccordingToSchema(name, item));
205         }
206
207         return result;
208
209     }
210
211     /**
212      *
213      * @param name the property name you'd like to set the value of
214      * @param obj the value to be set
215      * @return
216      */
217     public void setValue(String name, Object obj) {
218         Object box = this.castValueAccordingToSchema(name, obj);
219
220         name = convertPropertyName(name);
221         this.set(name, box);
222     }
223
224     /**
225      *
226      * @return a list of all the properties available on the object
227      */
228     public abstract Set<String> getProperties();
229
230     public Set<String> getProperties(PropertyPredicate<Introspector, String> p) {
231         final Set<String> temp = new LinkedHashSet<>();
232         this.getProperties().stream().filter(item -> {
233             return p.test(this, item);
234         }).forEach(item -> {
235             temp.add(item);
236         });
237         final Set<String> result = Collections.unmodifiableSet(temp);
238
239         return result;
240
241     }
242
243     public Set<String> getSimpleProperties(PropertyPredicate<Introspector, String> p) {
244         return this.getProperties().stream().filter(item -> p.test(this, item)).filter(this::isSimpleType)
245                 .collect(Collectors.toSet());
246     }
247
248     /**
249      *
250      * @return a list of the required properties on the object
251      */
252     public abstract Set<String> getRequiredProperties();
253
254     /**
255      *
256      * @return a list of the properties that can be used to query the object in the db
257      */
258     public abstract Set<String> getKeys();
259
260     /**
261      *
262      * @return a list of the all key properties for this object
263      */
264     public Set<String> getAllKeys() {
265         Set<String> result = null;
266         if (this.allKeys == null) {
267             Set<String> keys = this.getKeys();
268             result = new LinkedHashSet<>();
269             result.addAll(keys);
270             String altKeys = this.getMetadata(ObjectMetadata.ALTERNATE_KEYS_1);
271             if (altKeys != null) {
272                 String[] altKeysArray = altKeys.split(",");
273                 for (String altKey : altKeysArray) {
274                     result.add(altKey);
275                 }
276             }
277             result = Collections.unmodifiableSet(result);
278             this.allKeys = result;
279         }
280         result = this.allKeys;
281         return result;
282     }
283
284     public Set<String> getIndexedProperties() {
285         Set<String> result = null;
286
287         if (this.indexedProperties == null) {
288             result = new LinkedHashSet<>();
289             Set<String> keys = this.getKeys();
290             result.addAll(keys);
291             String altKeys = this.getMetadata(ObjectMetadata.INDEXED_PROPS);
292             if (altKeys != null) {
293                 String[] altKeysArray = altKeys.split(",");
294                 for (String altKey : altKeysArray) {
295                     result.add(altKey);
296                 }
297             }
298             this.indexedProperties = Collections.unmodifiableSet(result);
299         }
300         result = this.indexedProperties;
301         return result;
302     }
303
304     public Set<String> getUniqueProperties() {
305         Set<String> result = null;
306         if (this.uniqueProperties == null) {
307             String altKeys = this.getMetadata(ObjectMetadata.UNIQUE_PROPS);
308             result = new LinkedHashSet<>();
309             if (altKeys != null) {
310                 String[] altKeysArray = altKeys.split(",");
311                 for (String altKey : altKeysArray) {
312                     result.add(altKey);
313                 }
314             }
315             this.uniqueProperties = Collections.unmodifiableSet(result);
316
317         }
318         result = this.uniqueProperties;
319         return result;
320     }
321
322     public Set<String> getDependentOn() {
323         String dependentOn = this.getMetadata(ObjectMetadata.DEPENDENT_ON);
324         if (dependentOn == null) {
325             return new LinkedHashSet<>();
326         }
327         return new LinkedHashSet<>(Arrays.asList(dependentOn.split(",")));
328     }
329
330     /**
331      *
332      * @param name
333      * @return the string name of the java class of the named property
334      */
335     public String getType(String name) {
336         Class<?> resultClass = this.getClass(name);
337         String result = "";
338
339         if (resultClass != null) {
340             result = resultClass.getName();
341             if (result.equals("java.util.ArrayList")) {
342                 result = "java.util.List";
343             }
344         }
345
346         return result;
347     }
348
349     /**
350      * This will returned the generic parameterized type of the underlying
351      * object if it exists
352      *
353      * @param name
354      * @return the generic type of the java class of the underlying object
355      */
356     public String getGenericType(String name) {
357         Class<?> resultClass = this.getGenericTypeClass(name);
358         String result = "";
359
360         if (resultClass != null) {
361             result = resultClass.getName();
362         }
363
364         return result;
365     }
366
367     /**
368      *
369      * @return the string name of the java class of the underlying object
370      */
371     public abstract String getJavaClassName();
372
373     /**
374      *
375      * @param name the property name
376      * @return the Class object
377      */
378     public abstract Class<?> getClass(String name);
379
380     public abstract Class<?> getGenericTypeClass(String name);
381
382     /**
383      *
384      * @param name the property name
385      * @return a new instance of the underlying type of this property
386      * @throws AAIUnknownObjectException
387      */
388     public Object newInstanceOfProperty(String name) throws AAIUnknownObjectException {
389         String type = this.getType(name);
390         return loader.objectFromName(type);
391     }
392
393     public Object newInstanceOfNestedProperty(String name) throws AAIUnknownObjectException {
394         String type = this.getGenericType(name);
395         return loader.objectFromName(type);
396     }
397
398     public Introspector newIntrospectorInstanceOfProperty(String name) throws AAIUnknownObjectException {
399
400         Introspector result = IntrospectorFactory.newInstance(this.getModelType(), this.newInstanceOfProperty(name));
401
402         return result;
403
404     }
405
406     public Introspector newIntrospectorInstanceOfNestedProperty(String name) throws AAIUnknownObjectException {
407
408         Introspector result =
409                 IntrospectorFactory.newInstance(this.getModelType(), this.newInstanceOfNestedProperty(name));
410
411         return result;
412
413     }
414
415     /**
416      * Is this type not a Java String or primitive
417      *
418      * @param name
419      * @return
420      */
421     public boolean isComplexType(String name) {
422         String result = this.getType(name);
423
424         if (result.contains("aai") || result.equals("java.lang.Object")) {
425             return true;
426         } else {
427             return false;
428         }
429     }
430
431     public boolean isComplexGenericType(String name) {
432         String result = this.getGenericType(name);
433
434         if (result.contains("aai")) {
435             return true;
436         } else {
437             return false;
438         }
439     }
440
441     public boolean isSimpleType(String name) {
442         return !(this.isComplexType(name) || this.isListType(name));
443     }
444
445     public boolean isSimpleGenericType(String name) {
446         return !this.isComplexGenericType(name);
447     }
448
449     public boolean isListType(String name) {
450         String result = this.getType(name);
451
452         if (result.contains("java.util.List")) {
453             return true;
454         } else {
455             return false;
456         }
457     }
458
459     public boolean isContainer() {
460         Set<String> props = this.getProperties();
461         boolean result = false;
462         if (props.size() == 1 && this.isListType(props.iterator().next())) {
463             result = true;
464         }
465
466         return result;
467     }
468
469     public abstract String getChildName();
470
471     public String getChildDBName() {
472         String result = this.getChildName();
473
474         result = namingException.getDBName(result);
475         return result;
476     }
477
478     public abstract String getName();
479
480     public String getDbName() {
481         String lowerHyphen = this.getName();
482
483         lowerHyphen = namingException.getDBName(lowerHyphen);
484
485         return lowerHyphen;
486     }
487
488     public abstract ModelType getModelType();
489
490     public boolean hasChild(Introspector child) {
491         boolean result = false;
492         // check all inheriting types for this child
493         if ("true".equals(this.getMetadata(ObjectMetadata.ABSTRACT))) {
494             String[] inheritors = this.getMetadata(ObjectMetadata.INHERITORS).split(",");
495             for (String inheritor : inheritors) {
496                 try {
497                     Introspector temp = this.loader.introspectorFromName(inheritor);
498                     result = temp.hasProperty(child.getName());
499                     if (result) {
500                         break;
501                     }
502                 } catch (AAIUnknownObjectException e) {
503                     LOGGER.warn(
504                             "Skipping inheritor " + inheritor + " (Unknown Object) " + LogFormatTools.getStackTop(e));
505                 }
506             }
507         } else {
508             result = this.hasProperty(child.getName());
509         }
510         return result;
511     }
512
513     public void setURIChain(String uri) {
514         this.uriChain = uri;
515     }
516
517     public abstract String getObjectId() throws UnsupportedEncodingException;
518
519     public String getURI() throws UnsupportedEncodingException {
520         // String result = this.uriChain;
521         String result = "";
522         String namespace = this.getMetadata(ObjectMetadata.NAMESPACE);
523         String container = this.getMetadata(ObjectMetadata.CONTAINER);
524         if (this.isContainer()) {
525             result += "/" + this.getName();
526         } else {
527
528             if (container != null) {
529                 result += "/" + container;
530             }
531             result += "/" + this.getDbName() + "/" + this.findKey();
532
533             if (namespace != null && !namespace.equals("")) {
534                 result = "/" + namespace + result;
535             }
536         }
537
538         return result;
539     }
540
541     public String getGenericURI() {
542         String result = "";
543         if (this.isContainer()) {
544             result += "/" + this.getName();
545         } else {
546             result += "/" + this.getDbName();
547             for (String key : this.getKeys()) {
548                 result += "/{" + this.getDbName() + "-" + key + "}";
549             }
550         }
551
552         return result;
553     }
554
555     public String getFullGenericURI() {
556         String result = "";
557         String namespace = this.getMetadata(ObjectMetadata.NAMESPACE);
558         String container = this.getMetadata(ObjectMetadata.CONTAINER);
559         if (this.isContainer()) {
560             result += "/" + this.getName();
561         } else {
562
563             if (container != null) {
564                 result += "/" + container;
565             }
566             result += "/" + this.getDbName();
567
568             for (String key : this.getKeys()) {
569                 result += "/{" + this.getDbName() + "-" + key + "}";
570             }
571             if (namespace != null && !namespace.equals("")) {
572                 result = "/" + namespace + result;
573             }
574
575         }
576
577         return result;
578     }
579
580     public abstract String preProcessKey(String key);
581
582     protected abstract String findKey() throws UnsupportedEncodingException;
583
584     public abstract String marshal(MarshallerProperties properties);
585
586     public abstract Object getUnderlyingObject();
587
588     public String marshal(boolean formatted) {
589         MarshallerProperties properties =
590                 new MarshallerProperties.Builder(MediaType.APPLICATION_JSON_TYPE).formatted(formatted).build();
591
592         return marshal(properties);
593     }
594
595     public String makeSingular(String word) {
596
597         String result = word;
598         result = result.replaceAll("(?:([ho])es|s)$", "");
599
600         if (result.equals("ClassesOfService")) {
601             result = "ClassOfService";
602         } else if (result.equals("CvlanTag")) {
603             result = "CvlanTagEntry";
604         } else if (result.equals("Metadata")) {
605             result = "Metadatum";
606         }
607         return result;
608     }
609
610     protected String makePlural(String word) {
611         String result = word;
612
613         if (result.equals("cvlan-tag-entry")) {
614             return "cvlan-tags";
615         } else if (result.equals("class-of-service")) {
616             return "classes-of-service";
617         } else if (result.equals("metadatum")) {
618             return "metadata";
619         }
620         result = result.replaceAll("([a-z])$", "$1s");
621         result = result.replaceAll("([hox])s$", "$1es");
622         /*
623          * if (result.equals("classes-of-services")) {
624          * result = "classes-of-service";
625          * }
626          */
627
628         return result;
629     }
630
631     public abstract String getMetadata(ObjectMetadata metadataName);
632
633     public abstract Map<PropertyMetadata, String> getPropertyMetadata(String propName);
634
635     public Optional<String> getPropertyMetadata(String propName, PropertyMetadata metadataName) {
636         final String resultValue = this.getPropertyMetadata(propName).getOrDefault(metadataName, "");
637         Optional<String> result = Optional.empty();
638
639         if (!resultValue.isEmpty()) {
640             result = Optional.of(resultValue);
641         }
642         return result;
643
644     }
645
646     public abstract SchemaVersion getVersion();
647
648     public Loader getLoader() {
649         return this.loader;
650     }
651
652     public boolean isTopLevel() {
653
654         return this.getMetadata(ObjectMetadata.NAMESPACE) != null;
655     }
656
657 }