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