Enhancements for the aai-common library
[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 org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
25 import com.google.common.base.CaseFormat;
26 import org.apache.commons.lang.ClassUtils;
27 import org.eclipse.persistence.exceptions.DynamicException;
28 import org.onap.aai.config.SpringContextAware;
29 import org.onap.aai.introspection.exceptions.AAIUnknownObjectException;
30 import org.onap.aai.logging.ErrorLogHelper;
31 import org.onap.aai.logging.LogFormatTools;
32 import org.onap.aai.nodes.CaseFormatStore;
33 import org.onap.aai.nodes.NodeIngestor;
34 import org.onap.aai.restcore.MediaType;
35 import org.onap.aai.schema.enums.ObjectMetadata;
36 import org.onap.aai.schema.enums.PropertyMetadata;
37 import org.onap.aai.setup.SchemaVersion;
38 import org.onap.aai.workarounds.NamingExceptions;
39
40 import java.io.UnsupportedEncodingException;
41 import java.lang.reflect.InvocationTargetException;
42 import java.util.*;
43 import java.util.stream.Collectors;
44
45 public abstract class Introspector implements Cloneable {
46
47     private static final Logger LOGGER = LoggerFactory.getLogger(Introspector.class);
48
49     protected String className;
50     protected String uriChain = "";
51     protected Loader loader;
52     protected final NamingExceptions namingException = NamingExceptions.getInstance();
53     private Set<String> uniqueProperties = null;
54     private Set<String> indexedProperties = null;
55     private Set<String> allKeys = null;
56     private Set<String> dslStartNodeProperties = 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 (!obj.getClass().getName().equals(nameClass.getName())) {
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 -> p.test(this, item)).forEach(temp::add);
233         return Collections.unmodifiableSet(temp);
234     }
235
236     public Set<String> getSimpleProperties(PropertyPredicate<Introspector, String> p) {
237         return this.getProperties().stream().filter(item -> p.test(this, item)).filter(this::isSimpleType)
238                 .collect(Collectors.toSet());
239     }
240
241     /**
242      *
243      * @return a list of the required properties on the object
244      */
245     public abstract Set<String> getRequiredProperties();
246
247     /**
248      *
249      * @return a list of the properties that can be used to query the object in the db
250      */
251     public abstract Set<String> getKeys();
252
253     /**
254      *
255      * @return a list of the all key properties for this object
256      */
257     public Set<String> getAllKeys() {
258         Set<String> result = null;
259         if (this.allKeys == null) {
260             Set<String> keys = this.getKeys();
261             result = new LinkedHashSet<>();
262             result.addAll(keys);
263             String altKeys = this.getMetadata(ObjectMetadata.ALTERNATE_KEYS_1);
264             if (altKeys != null) {
265                 String[] altKeysArray = altKeys.split(",");
266                 for (String altKey : altKeysArray) {
267                     result.add(altKey);
268                 }
269             }
270             result = Collections.unmodifiableSet(result);
271             this.allKeys = result;
272         }
273         result = this.allKeys;
274         return result;
275     }
276
277     public Set<String> getIndexedProperties() {
278         Set<String> result = null;
279
280         if (this.indexedProperties == null) {
281             result = new LinkedHashSet<>();
282             Set<String> keys = this.getKeys();
283             result.addAll(keys);
284             String altKeys = this.getMetadata(ObjectMetadata.INDEXED_PROPS);
285             if (altKeys != null) {
286                 String[] altKeysArray = altKeys.split(",");
287                 for (String altKey : altKeysArray) {
288                     result.add(altKey);
289                 }
290             }
291             this.indexedProperties = Collections.unmodifiableSet(result);
292         }
293         result = this.indexedProperties;
294         return result;
295     }
296
297     public Set<String> getDslStartNodeProperties() {
298         Set<String> result = null;
299
300         if (this.dslStartNodeProperties == null) {
301             /*
302              * The dslStartNodeProperties will have keys by default
303              * If dslStartNodeProps exist in the oxm use it
304              * if not use the indexedProps
305              */
306             result = new LinkedHashSet<>(this.getKeys());
307
308             String dslKeys = this.getMetadata(ObjectMetadata.DSL_START_NODE_PROPS);
309             String indexedKeys = this.getMetadata(ObjectMetadata.INDEXED_PROPS);
310             if (dslKeys != null) {
311                 Arrays.stream(dslKeys.split(",")).forEach(result::add);
312             }
313             else if(indexedKeys != null){
314                 Arrays.stream(indexedKeys.split(",")).forEach(result::add);
315             }
316             this.dslStartNodeProperties = Collections.unmodifiableSet(result);
317         }
318         result = this.dslStartNodeProperties;
319         return result;
320     }
321
322     public Set<String> getUniqueProperties() {
323         Set<String> result = null;
324         if (this.uniqueProperties == null) {
325             String altKeys = this.getMetadata(ObjectMetadata.UNIQUE_PROPS);
326             result = new LinkedHashSet<>();
327             if (altKeys != null) {
328                 String[] altKeysArray = altKeys.split(",");
329                 for (String altKey : altKeysArray) {
330                     result.add(altKey);
331                 }
332             }
333             this.uniqueProperties = Collections.unmodifiableSet(result);
334
335         }
336         result = this.uniqueProperties;
337         return result;
338     }
339
340     public Set<String> getDependentOn() {
341         String dependentOn = this.getMetadata(ObjectMetadata.DEPENDENT_ON);
342         if (dependentOn == null) {
343             return new LinkedHashSet<>();
344         }
345         return new LinkedHashSet<>(Arrays.asList(dependentOn.split(",")));
346     }
347
348     /**
349      *
350      * @param name
351      * @return the string name of the java class of the named property
352      */
353     public String getType(String name) {
354         Class<?> resultClass = this.getClass(name);
355         String result = "";
356
357         if (resultClass != null) {
358             result = resultClass.getName();
359             if (result.equals("java.util.ArrayList")) {
360                 result = "java.util.List";
361             }
362         }
363
364         return result;
365     }
366
367     /**
368      * This will returned the generic parameterized type of the underlying
369      * object if it exists
370      *
371      * @param name
372      * @return the generic type of the java class of the underlying object
373      */
374     public String getGenericType(String name) {
375         Class<?> resultClass = this.getGenericTypeClass(name);
376         String result = "";
377
378         if (resultClass != null) {
379             result = resultClass.getName();
380         }
381
382         return result;
383     }
384
385     /**
386      *
387      * @return the string name of the java class of the underlying object
388      */
389     public abstract String getJavaClassName();
390
391     /**
392      *
393      * @param name the property name
394      * @return the Class object
395      */
396     public abstract Class<?> getClass(String name);
397
398     public abstract Class<?> getGenericTypeClass(String name);
399
400     /**
401      *
402      * @param name the property name
403      * @return a new instance of the underlying type of this property
404      * @throws AAIUnknownObjectException
405      */
406     public Object newInstanceOfProperty(String name) throws AAIUnknownObjectException {
407         String type = this.getType(name);
408         return loader.objectFromName(type);
409     }
410
411     public Object newInstanceOfNestedProperty(String name) throws AAIUnknownObjectException {
412         String type = this.getGenericType(name);
413         return loader.objectFromName(type);
414     }
415
416     public Introspector newIntrospectorInstanceOfProperty(String name) throws AAIUnknownObjectException {
417
418         Introspector result = IntrospectorFactory.newInstance(this.getModelType(), this.newInstanceOfProperty(name));
419
420         return result;
421
422     }
423
424     public Introspector newIntrospectorInstanceOfNestedProperty(String name) throws AAIUnknownObjectException {
425
426         Introspector result =
427                 IntrospectorFactory.newInstance(this.getModelType(), this.newInstanceOfNestedProperty(name));
428
429         return result;
430
431     }
432
433     /**
434      * Is this type not a Java String or primitive
435      *
436      * @param name
437      * @return
438      */
439     public boolean isComplexType(String name) {
440         String result = this.getType(name);
441
442         if (result.contains("aai") || result.equals("java.lang.Object")) {
443             return true;
444         } else {
445             return false;
446         }
447     }
448
449     public boolean isComplexGenericType(String name) {
450         String result = this.getGenericType(name);
451
452         if (result.contains("aai")) {
453             return true;
454         } else {
455             return false;
456         }
457     }
458
459     public boolean isSimpleType(String name) {
460         return !(this.isComplexType(name) || this.isListType(name));
461     }
462
463     public boolean isSimpleGenericType(String name) {
464         return !this.isComplexGenericType(name);
465     }
466
467     public boolean isListType(String name) {
468         String result = this.getType(name);
469
470         if (result.contains("java.util.List")) {
471             return true;
472         } else {
473             return false;
474         }
475     }
476
477     public boolean isContainer() {
478         Set<String> props = this.getProperties();
479         boolean result = false;
480         if (props.size() == 1 && this.isListType(props.iterator().next())) {
481             result = true;
482         }
483
484         return result;
485     }
486
487     public abstract String getChildName();
488
489     public String getChildDBName() {
490         String result = this.getChildName();
491
492         result = namingException.getDBName(result);
493         return result;
494     }
495
496     public abstract String getName();
497
498     public String getDbName() {
499         String lowerHyphen = this.getName();
500
501         lowerHyphen = namingException.getDBName(lowerHyphen);
502
503         return lowerHyphen;
504     }
505
506     public abstract ModelType getModelType();
507
508     public boolean hasChild(Introspector child) {
509         boolean result = false;
510         // check all inheriting types for this child
511         if ("true".equals(this.getMetadata(ObjectMetadata.ABSTRACT))) {
512             String[] inheritors = this.getMetadata(ObjectMetadata.INHERITORS).split(",");
513             for (String inheritor : inheritors) {
514                 try {
515                     Introspector temp = this.loader.introspectorFromName(inheritor);
516                     result = temp.hasProperty(child.getName());
517                     if (result) {
518                         break;
519                     }
520                 } catch (AAIUnknownObjectException e) {
521                     LOGGER.warn(
522                             "Skipping inheritor " + inheritor + " (Unknown Object) " + LogFormatTools.getStackTop(e));
523                 }
524             }
525         } else {
526             result = this.hasProperty(child.getName());
527         }
528         return result;
529     }
530
531     public void setURIChain(String uri) {
532         this.uriChain = uri;
533     }
534
535     public abstract String getObjectId() throws UnsupportedEncodingException;
536
537     public String getURI() throws UnsupportedEncodingException {
538         // String result = this.uriChain;
539         String result = "";
540         String namespace = this.getMetadata(ObjectMetadata.NAMESPACE);
541         String container = this.getMetadata(ObjectMetadata.CONTAINER);
542         if (this.isContainer()) {
543             result += "/" + this.getName();
544         } else {
545
546             if (container != null) {
547                 result += "/" + container;
548             }
549             result += "/" + this.getDbName() + "/" + this.findKey();
550
551             if (namespace != null && !namespace.equals("")) {
552                 result = "/" + namespace + result;
553             }
554         }
555
556         return result;
557     }
558
559     public String getGenericURI() {
560         String result = "";
561         if (this.isContainer()) {
562             result += "/" + this.getName();
563         } else {
564             result += "/" + this.getDbName();
565             for (String key : this.getKeys()) {
566                 result += "/{" + this.getDbName() + "-" + key + "}";
567             }
568         }
569
570         return result;
571     }
572
573     public String getFullGenericURI() {
574         String result = "";
575         String namespace = this.getMetadata(ObjectMetadata.NAMESPACE);
576         String container = this.getMetadata(ObjectMetadata.CONTAINER);
577         if (this.isContainer()) {
578             result += "/" + this.getName();
579         } else {
580
581             if (container != null) {
582                 result += "/" + container;
583             }
584             result += "/" + this.getDbName();
585
586             for (String key : this.getKeys()) {
587                 result += "/{" + this.getDbName() + "-" + key + "}";
588             }
589             if (namespace != null && !namespace.equals("")) {
590                 result = "/" + namespace + result;
591             }
592
593         }
594
595         return result;
596     }
597
598     public abstract String preProcessKey(String key);
599
600     protected abstract String findKey() throws UnsupportedEncodingException;
601
602     public abstract String marshal(MarshallerProperties properties);
603
604     public abstract Object getUnderlyingObject();
605
606     public String marshal(boolean formatted) {
607         MarshallerProperties properties =
608                 new MarshallerProperties.Builder(MediaType.APPLICATION_JSON_TYPE).formatted(formatted).build();
609
610         return marshal(properties);
611     }
612
613     public String makeSingular(String word) {
614
615         String result = word;
616         result = result.replaceAll("(?:([ho])es|s)$", "");
617
618         if (result.equals("ClassesOfService")) {
619             result = "ClassOfService";
620         } else if (result.equals("CvlanTag")) {
621             result = "CvlanTagEntry";
622         } else if (result.equals("Metadata")) {
623             result = "Metadatum";
624         }
625         return result;
626     }
627
628     protected String makePlural(String word) {
629         String result = word;
630
631         if (result.equals("cvlan-tag-entry")) {
632             return "cvlan-tags";
633         } else if (result.equals("class-of-service")) {
634             return "classes-of-service";
635         } else if (result.equals("metadatum")) {
636             return "metadata";
637         }
638         result = result.replaceAll("([a-z])$", "$1s");
639         result = result.replaceAll("([hox])s$", "$1es");
640         /*
641          * if (result.equals("classes-of-services")) {
642          * result = "classes-of-service";
643          * }
644          */
645
646         return result;
647     }
648
649     public abstract String getMetadata(ObjectMetadata metadataName);
650
651     public abstract Map<PropertyMetadata, String> getPropertyMetadata(String propName);
652
653     public Optional<String> getPropertyMetadata(String propName, PropertyMetadata metadataName) {
654         final String resultValue = this.getPropertyMetadata(propName).getOrDefault(metadataName, "");
655         Optional<String> result = Optional.empty();
656
657         if (!resultValue.isEmpty()) {
658             result = Optional.of(resultValue);
659         }
660         return result;
661
662     }
663
664     public abstract SchemaVersion getVersion();
665
666     public Loader getLoader() {
667         return this.loader;
668     }
669
670     public boolean isTopLevel() {
671
672         return this.getMetadata(ObjectMetadata.NAMESPACE) != null;
673     }
674
675 }