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