2 * ============LICENSE_START=======================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 package org.onap.aai.introspection;
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;
32 import java.util.Optional;
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;
51 public abstract class Introspector implements Cloneable {
53 private static final Logger LOGGER = LoggerFactory.getLogger(Introspector.class);
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;
64 protected CaseFormatStore caseFormatStore = null;
65 protected NodeIngestor nodeIngestor;
67 protected Introspector(Object obj) {
68 this.nodeIngestor = SpringContextAware.getBean(NodeIngestor.class);
69 this.caseFormatStore = nodeIngestor.getCaseFormatStore();
72 public abstract boolean hasProperty(String name);
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);
81 protected abstract Object get(String name);
83 protected abstract void set(String name, Object value);
87 * @param name the property name you'd like to retrieve the value for
88 * @return the value of the property
90 public <T> T getValue(String name) {
91 String convertedName = convertPropertyName(name);
94 if (this.hasProperty(name)) {
95 result = this.get(convertedName);
97 /* property not found - slightly ambiguous */
101 Class<?> clazz = this.getClass(name);
102 if (this.isListType(name) && result == null) {
104 this.set(convertedName, clazz.newInstance());
105 result = this.get(convertedName);
106 } catch (DynamicException | InstantiationException | IllegalAccessException e) {
107 LOGGER.warn(e.getMessage(), e);
114 public Introspector getWrappedValue(String name) {
115 String convertedName = convertPropertyName(name);
118 if (this.hasProperty(name)) {
119 value = this.get(convertedName);
121 /* property not found - slightly ambiguous */
125 Class<?> clazz = this.getClass(name);
126 if (this.isListType(name) && value == null) {
128 this.set(convertedName, clazz.newInstance());
129 value = this.get(convertedName);
130 } catch (DynamicException | InstantiationException | IllegalAccessException e) {
131 LOGGER.warn(e.getMessage(), e);
135 return IntrospectorFactory.newInstance(this.getModelType(), value);
143 public List<Introspector> getWrappedListValue(String name) {
144 String convertedName = convertPropertyName(name);
146 List<Introspector> resultList = new ArrayList<>();
147 if (this.hasProperty(name)) {
148 value = this.get(convertedName);
150 /* property not found - slightly ambiguous */
153 boolean isListType = this.isListType(name);
154 if (!this.isListType(name)) {
157 Class<?> clazz = this.getClass(name);
158 if (isListType && value == null) {
160 this.set(convertedName, clazz.newInstance());
161 value = this.get(convertedName);
162 } catch (DynamicException | InstantiationException | IllegalAccessException e) {
163 LOGGER.warn(e.getMessage(), e);
167 List<Object> valueList = (List<Object>) value;
169 for (Object item : valueList) {
170 resultList.add(IntrospectorFactory.newInstance(this.getModelType(), item));
177 public Object castValueAccordingToSchema(String name, Object obj) {
179 Class<?> nameClass = this.getClass(name);
180 if (nameClass == null) {
181 throw new IllegalArgumentException("property: " + name + " does not exist on " + this.getDbName());
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());
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());
198 } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
199 | InvocationTargetException | NoSuchMethodException | SecurityException e) {
200 ErrorLogHelper.logError("AAI_4017", e.getMessage());
206 public List<Object> castValueAccordingToSchema(String name, List<?> objs) {
207 List<Object> result = new ArrayList<>();
209 for (Object item : objs) {
210 result.add(this.castValueAccordingToSchema(name, item));
219 * @param name the property name you'd like to set the value of
220 * @param obj the value to be set
223 public void setValue(String name, Object obj) {
224 Object box = this.castValueAccordingToSchema(name, obj);
226 name = convertPropertyName(name);
232 * @return a list of all the properties available on the object
234 public abstract Set<String> getProperties();
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);
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());
249 * @return a list of the required properties on the object
251 public abstract Set<String> getRequiredProperties();
255 * @return a list of the properties that can be used to query the object in the db
257 public abstract Set<String> getKeys();
261 * @return a list of the all key properties for this object
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<>();
269 String altKeys = this.getMetadata(ObjectMetadata.ALTERNATE_KEYS_1);
270 if (altKeys != null) {
271 String[] altKeysArray = altKeys.split(",");
272 for (String altKey : altKeysArray) {
276 result = Collections.unmodifiableSet(result);
277 this.allKeys = result;
279 result = this.allKeys;
283 public Set<String> getIndexedProperties() {
284 Set<String> result = null;
286 if (this.indexedProperties == null) {
287 result = new LinkedHashSet<>();
288 Set<String> keys = this.getKeys();
290 String altKeys = this.getMetadata(ObjectMetadata.INDEXED_PROPS);
291 if (altKeys != null) {
292 String[] altKeysArray = altKeys.split(",");
293 for (String altKey : altKeysArray) {
297 this.indexedProperties = Collections.unmodifiableSet(result);
299 result = this.indexedProperties;
303 public Set<String> getDslStartNodeProperties() {
304 Set<String> result = null;
306 if (this.dslStartNodeProperties == null) {
308 * The dslStartNodeProperties will have keys by default
309 * If dslStartNodeProps exist in the oxm use it
310 * if not use the indexedProps
312 result = new LinkedHashSet<>(this.getKeys());
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);
319 else if(indexedKeys != null){
320 Arrays.stream(indexedKeys.split(",")).forEach(result::add);
322 this.dslStartNodeProperties = Collections.unmodifiableSet(result);
324 result = this.dslStartNodeProperties;
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) {
339 this.uniqueProperties = Collections.unmodifiableSet(result);
342 result = this.uniqueProperties;
346 public Set<String> getDependentOn() {
347 String dependentOn = this.getMetadata(ObjectMetadata.DEPENDENT_ON);
348 if (dependentOn == null) {
349 return new LinkedHashSet<>();
351 return new LinkedHashSet<>(Arrays.asList(dependentOn.split(",")));
357 * @return the string name of the java class of the named property
359 public String getType(String name) {
360 Class<?> resultClass = this.getClass(name);
363 if (resultClass != null) {
364 result = resultClass.getName();
365 if (result.equals("java.util.ArrayList")) {
366 result = "java.util.List";
374 * This will returned the generic parameterized type of the underlying
375 * object if it exists
378 * @return the generic type of the java class of the underlying object
380 public String getGenericType(String name) {
381 Class<?> resultClass = this.getGenericTypeClass(name);
384 if (resultClass != null) {
385 result = resultClass.getName();
393 * @return the string name of the java class of the underlying object
395 public abstract String getJavaClassName();
399 * @param name the property name
400 * @return the Class object
402 public abstract Class<?> getClass(String name);
404 public abstract Class<?> getGenericTypeClass(String name);
408 * @param name the property name
409 * @return a new instance of the underlying type of this property
410 * @throws AAIUnknownObjectException
412 public Object newInstanceOfProperty(String name) throws AAIUnknownObjectException {
413 String type = this.getType(name);
414 return loader.objectFromName(type);
417 public Object newInstanceOfNestedProperty(String name) throws AAIUnknownObjectException {
418 String type = this.getGenericType(name);
419 return loader.objectFromName(type);
422 public Introspector newIntrospectorInstanceOfProperty(String name) throws AAIUnknownObjectException {
424 Introspector result = IntrospectorFactory.newInstance(this.getModelType(), this.newInstanceOfProperty(name));
430 public Introspector newIntrospectorInstanceOfNestedProperty(String name) throws AAIUnknownObjectException {
432 Introspector result =
433 IntrospectorFactory.newInstance(this.getModelType(), this.newInstanceOfNestedProperty(name));
440 * Is this type not a Java String or primitive
445 public boolean isComplexType(String name) {
446 String result = this.getType(name);
448 if (result.contains("aai") || result.equals("java.lang.Object")) {
455 public boolean isComplexGenericType(String name) {
456 String result = this.getGenericType(name);
458 if (result.contains("aai")) {
465 public boolean isSimpleType(String name) {
466 return !(this.isComplexType(name) || this.isListType(name));
469 public boolean isSimpleGenericType(String name) {
470 return !this.isComplexGenericType(name);
473 public boolean isListType(String name) {
474 String result = this.getType(name);
476 if (result.contains("java.util.List")) {
483 public boolean isContainer() {
484 Set<String> props = this.getProperties();
485 boolean result = false;
486 if (props.size() == 1 && this.isListType(props.iterator().next())) {
493 public abstract String getChildName();
495 public String getChildDBName() {
496 String result = this.getChildName();
498 result = namingException.getDBName(result);
502 public abstract String getName();
504 public String getDbName() {
505 String lowerHyphen = this.getName();
507 lowerHyphen = namingException.getDBName(lowerHyphen);
512 public abstract ModelType getModelType();
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) {
521 Introspector temp = this.loader.introspectorFromName(inheritor);
522 result = temp.hasProperty(child.getName());
526 } catch (AAIUnknownObjectException e) {
528 "Skipping inheritor " + inheritor + " (Unknown Object) " + LogFormatTools.getStackTop(e));
532 result = this.hasProperty(child.getName());
537 public void setURIChain(String uri) {
541 public abstract String getObjectId() throws UnsupportedEncodingException;
543 public String getURI() throws UnsupportedEncodingException {
544 // String result = this.uriChain;
546 String namespace = this.getMetadata(ObjectMetadata.NAMESPACE);
547 String container = this.getMetadata(ObjectMetadata.CONTAINER);
548 if (this.isContainer()) {
549 result += "/" + this.getName();
552 if (container != null) {
553 result += "/" + container;
555 result += "/" + this.getDbName() + "/" + this.findKey();
557 if (namespace != null && !namespace.equals("")) {
558 result = "/" + namespace + result;
565 public String getGenericURI() {
567 if (this.isContainer()) {
568 result += "/" + this.getName();
570 result += "/" + this.getDbName();
571 for (String key : this.getKeys()) {
572 result += "/{" + this.getDbName() + "-" + key + "}";
579 public String getFullGenericURI() {
581 String namespace = this.getMetadata(ObjectMetadata.NAMESPACE);
582 String container = this.getMetadata(ObjectMetadata.CONTAINER);
583 if (this.isContainer()) {
584 result += "/" + this.getName();
587 if (container != null) {
588 result += "/" + container;
590 result += "/" + this.getDbName();
592 for (String key : this.getKeys()) {
593 result += "/{" + this.getDbName() + "-" + key + "}";
595 if (namespace != null && !namespace.equals("")) {
596 result = "/" + namespace + result;
604 public abstract String preProcessKey(String key);
606 protected abstract String findKey() throws UnsupportedEncodingException;
608 public abstract String marshal(MarshallerProperties properties);
610 public abstract Object getUnderlyingObject();
612 public String marshal(boolean formatted) {
613 MarshallerProperties properties =
614 new MarshallerProperties.Builder(MediaType.APPLICATION_JSON_TYPE).formatted(formatted).build();
616 return marshal(properties);
619 public String makeSingular(String word) {
621 String result = word;
622 result = result.replaceAll("(?:([ho])es|s)$", "");
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";
634 protected String makePlural(String word) {
635 String result = word;
637 if (result.equals("cvlan-tag-entry")) {
639 } else if (result.equals("class-of-service")) {
640 return "classes-of-service";
641 } else if (result.equals("metadatum")) {
644 result = result.replaceAll("([a-z])$", "$1s");
645 result = result.replaceAll("([hox])s$", "$1es");
647 * if (result.equals("classes-of-services")) {
648 * result = "classes-of-service";
655 public abstract String getMetadata(ObjectMetadata metadataName);
657 public abstract Map<PropertyMetadata, String> getPropertyMetadata(String propName);
659 public Optional<String> getPropertyMetadata(String propName, PropertyMetadata metadataName) {
660 final String resultValue = this.getPropertyMetadata(propName).getOrDefault(metadataName, "");
661 Optional<String> result = Optional.empty();
663 if (!resultValue.isEmpty()) {
664 result = Optional.of(resultValue);
670 public abstract SchemaVersion getVersion();
672 public Loader getLoader() {
676 public boolean isTopLevel() {
678 return this.getMetadata(ObjectMetadata.NAMESPACE) != null;