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