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