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;
25 import java.io.UnsupportedEncodingException;
26 import java.lang.reflect.InvocationTargetException;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.LinkedHashSet;
31 import java.util.List;
33 import java.util.Optional;
35 import java.util.stream.Collectors;
37 import org.apache.commons.lang3.ClassUtils;
38 import org.eclipse.persistence.exceptions.DynamicException;
39 import org.onap.aai.config.SpringContextAware;
40 import org.onap.aai.introspection.exceptions.AAIUnknownObjectException;
41 import org.onap.aai.logging.ErrorLogHelper;
42 import org.onap.aai.logging.LogFormatTools;
43 import org.onap.aai.nodes.CaseFormatStore;
44 import org.onap.aai.nodes.NodeIngestor;
45 import org.onap.aai.restcore.MediaType;
46 import org.onap.aai.schema.enums.ObjectMetadata;
47 import org.onap.aai.schema.enums.PropertyMetadata;
48 import org.onap.aai.setup.SchemaVersion;
49 import org.onap.aai.workarounds.NamingExceptions;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
53 public abstract class Introspector implements Cloneable {
55 private static final Logger LOGGER = LoggerFactory.getLogger(Introspector.class);
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;
66 protected CaseFormatStore caseFormatStore = null;
67 protected NodeIngestor nodeIngestor;
69 protected Introspector(Object obj) {
70 this.nodeIngestor = SpringContextAware.getBean(NodeIngestor.class);
71 this.caseFormatStore = nodeIngestor.getCaseFormatStore();
74 public abstract boolean hasProperty(String name);
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);
83 protected abstract Object get(String name);
85 protected abstract void set(String name, Object value);
89 * @param name the property name you'd like to retrieve the value for
90 * @return the value of the property
92 public <T> T getValue(String name) {
93 String convertedName = convertPropertyName(name);
96 if (this.hasProperty(name)) {
97 result = this.get(convertedName);
99 /* property not found - slightly ambiguous */
103 Class<?> clazz = this.getClass(name);
104 if (this.isListType(name) && result == null) {
106 this.set(convertedName, clazz.getDeclaredConstructor().newInstance());
107 result = this.get(convertedName);
108 } catch (DynamicException | InstantiationException | IllegalAccessException | IllegalArgumentException
109 | InvocationTargetException | NoSuchMethodException | SecurityException e) {
110 LOGGER.warn(e.getMessage(), e);
117 public Introspector getWrappedValue(String name) {
118 String convertedName = convertPropertyName(name);
121 if (this.hasProperty(name)) {
122 value = this.get(convertedName);
124 /* property not found - slightly ambiguous */
128 Class<?> clazz = this.getClass(name);
129 if (this.isListType(name) && value == null) {
131 this.set(convertedName, clazz.getDeclaredConstructor().newInstance());
132 value = this.get(convertedName);
133 } catch (DynamicException | InstantiationException | IllegalAccessException | IllegalArgumentException
134 | InvocationTargetException | NoSuchMethodException | SecurityException e) {
135 LOGGER.warn(e.getMessage(), e);
139 return IntrospectorFactory.newInstance(this.getModelType(), value);
147 public List<Introspector> getWrappedListValue(String name) {
148 String convertedName = convertPropertyName(name);
150 List<Introspector> resultList = new ArrayList<>();
151 if (this.hasProperty(name)) {
152 value = this.get(convertedName);
154 /* property not found - slightly ambiguous */
157 boolean isListType = this.isListType(name);
158 if (!this.isListType(name)) {
161 Class<?> clazz = this.getClass(name);
162 if (isListType && value == null) {
164 this.set(convertedName, clazz.getDeclaredConstructor().newInstance());
165 value = this.get(convertedName);
166 } catch (DynamicException | InstantiationException | IllegalAccessException | IllegalArgumentException
167 | InvocationTargetException | NoSuchMethodException | SecurityException e) {
168 LOGGER.warn(e.getMessage(), e);
172 List<Object> valueList = (List<Object>) value;
174 for (Object item : valueList) {
175 resultList.add(IntrospectorFactory.newInstance(this.getModelType(), item));
182 public Object castValueAccordingToSchema(String name, Object obj) {
184 Class<?> nameClass = this.getClass(name);
185 if (nameClass == null) {
186 throw new IllegalArgumentException("property: " + name + " does not exist on " + this.getDbName());
191 if (!(obj.getClass().getCanonicalName().equals(nameClass.getCanonicalName()))) {
192 if (nameClass.isPrimitive()) {
193 nameClass = ClassUtils.primitiveToWrapper(nameClass);
194 result = nameClass.getConstructor(String.class).newInstance(obj.toString());
196 if (obj instanceof String) {
197 result = nameClass.getConstructor(String.class).newInstance(obj);
198 } else if (!this.isListType(name) && !this.isComplexType(name)) {
199 // box = obj.toString();
200 result = nameClass.getConstructor(String.class).newInstance(obj.toString());
203 } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
204 | InvocationTargetException | NoSuchMethodException | SecurityException e) {
205 ErrorLogHelper.logError("AAI_4017", e.getMessage());
211 public List<Object> castValueAccordingToSchema(String name, List<?> objs) {
212 List<Object> result = new ArrayList<>();
214 for (Object item : objs) {
215 result.add(this.castValueAccordingToSchema(name, item));
224 * @param name the property name you'd like to set the value of
225 * @param obj the value to be set
228 public void setValue(String name, Object obj) {
229 Object box = this.castValueAccordingToSchema(name, obj);
231 name = convertPropertyName(name);
237 * @return a list of all the properties available on the object
239 public abstract Set<String> getProperties();
241 public Set<String> getProperties(PropertyPredicate<Introspector, String> p) {
242 final Set<String> temp = new LinkedHashSet<>();
243 this.getProperties().stream().filter(item -> p.test(this, item)).forEach(temp::add);
244 return Collections.unmodifiableSet(temp);
247 public Set<String> getSimpleProperties(PropertyPredicate<Introspector, String> p) {
248 return this.getProperties().stream().filter(item -> p.test(this, item)).filter(this::isSimpleType)
249 .collect(Collectors.toSet());
254 * @return a list of the required properties on the object
256 public abstract Set<String> getRequiredProperties();
260 * @return a list of the properties that can be used to query the object in the db
262 public abstract Set<String> getKeys();
266 * @return a list of the all key properties for this object
268 public Set<String> getAllKeys() {
269 Set<String> result = null;
270 if (this.allKeys == null) {
271 Set<String> keys = this.getKeys();
272 result = new LinkedHashSet<>();
274 String altKeys = this.getMetadata(ObjectMetadata.ALTERNATE_KEYS_1);
275 if (altKeys != null) {
276 String[] altKeysArray = altKeys.split(",");
277 for (String altKey : altKeysArray) {
281 result = Collections.unmodifiableSet(result);
282 this.allKeys = result;
284 result = this.allKeys;
288 public Set<String> getIndexedProperties() {
289 Set<String> result = null;
291 if (this.indexedProperties == null) {
292 result = new LinkedHashSet<>();
293 Set<String> keys = this.getKeys();
295 String altKeys = this.getMetadata(ObjectMetadata.INDEXED_PROPS);
296 if (altKeys != null) {
297 String[] altKeysArray = altKeys.split(",");
298 for (String altKey : altKeysArray) {
302 this.indexedProperties = Collections.unmodifiableSet(result);
304 result = this.indexedProperties;
308 public Set<String> getDslStartNodeProperties() {
309 Set<String> result = null;
311 if (this.dslStartNodeProperties == null) {
313 * The dslStartNodeProperties will have keys by default
314 * If dslStartNodeProps exist in the oxm use it
315 * if not use the indexedProps
317 result = new LinkedHashSet<>(this.getKeys());
319 String dslKeys = this.getMetadata(ObjectMetadata.DSL_START_NODE_PROPS);
320 String indexedKeys = this.getMetadata(ObjectMetadata.INDEXED_PROPS);
321 if (dslKeys != null) {
322 Arrays.stream(dslKeys.split(",")).forEach(result::add);
323 } else if (indexedKeys != null) {
324 Arrays.stream(indexedKeys.split(",")).forEach(result::add);
326 this.dslStartNodeProperties = Collections.unmodifiableSet(result);
328 result = this.dslStartNodeProperties;
332 public Set<String> getUniqueProperties() {
333 Set<String> result = null;
334 if (this.uniqueProperties == null) {
335 String altKeys = this.getMetadata(ObjectMetadata.UNIQUE_PROPS);
336 result = new LinkedHashSet<>();
337 if (altKeys != null) {
338 String[] altKeysArray = altKeys.split(",");
339 for (String altKey : altKeysArray) {
343 this.uniqueProperties = Collections.unmodifiableSet(result);
346 result = this.uniqueProperties;
350 public Set<String> getDependentOn() {
351 String dependentOn = this.getMetadata(ObjectMetadata.DEPENDENT_ON);
352 if (dependentOn == null) {
353 return new LinkedHashSet<>();
355 return new LinkedHashSet<>(Arrays.asList(dependentOn.split(",")));
361 * @return the string name of the java class of the named property
363 public String getType(String name) {
364 Class<?> resultClass = this.getClass(name);
367 if (resultClass != null) {
368 result = resultClass.getName();
369 if (result.equals("java.util.ArrayList")) {
370 result = "java.util.List";
378 * This will returned the generic parameterized type of the underlying
379 * object if it exists
382 * @return the generic type of the java class of the underlying object
384 public String getGenericType(String name) {
385 Class<?> resultClass = this.getGenericTypeClass(name);
388 if (resultClass != null) {
389 result = resultClass.getName();
397 * @return the string name of the java class of the underlying object
399 public abstract String getJavaClassName();
403 * @param name the property name
404 * @return the Class object
406 public abstract Class<?> getClass(String name);
408 public abstract Class<?> getGenericTypeClass(String name);
412 * @param name the property name
413 * @return a new instance of the underlying type of this property
414 * @throws AAIUnknownObjectException
416 public Object newInstanceOfProperty(String name) throws AAIUnknownObjectException {
417 String type = this.getType(name);
418 return loader.objectFromName(type);
421 public Object newInstanceOfNestedProperty(String name) throws AAIUnknownObjectException {
422 String type = this.getGenericType(name);
423 return loader.objectFromName(type);
426 public Introspector newIntrospectorInstanceOfProperty(String name) throws AAIUnknownObjectException {
428 Introspector result = IntrospectorFactory.newInstance(this.getModelType(), this.newInstanceOfProperty(name));
434 public Introspector newIntrospectorInstanceOfNestedProperty(String name) throws AAIUnknownObjectException {
436 Introspector result =
437 IntrospectorFactory.newInstance(this.getModelType(), this.newInstanceOfNestedProperty(name));
444 * Is this type not a Java String or primitive
449 public boolean isComplexType(String name) {
450 String result = this.getType(name);
452 if (result.contains("aai") || result.equals("java.lang.Object")) {
459 public boolean isComplexGenericType(String name) {
460 String result = this.getGenericType(name);
462 if (result.contains("aai")) {
469 public boolean isSimpleType(String name) {
470 return !(this.isComplexType(name) || this.isListType(name));
473 public boolean isSimpleGenericType(String name) {
474 return !this.isComplexGenericType(name);
477 public boolean isListType(String name) {
478 String result = this.getType(name);
480 if (result.contains("java.util.List")) {
487 public boolean isContainer() {
488 Set<String> props = this.getProperties();
489 boolean result = false;
490 if (props.size() == 1 && this.isListType(props.iterator().next())) {
497 public abstract String getChildName();
499 public String getChildDBName() {
500 String result = this.getChildName();
502 result = namingException.getDBName(result);
506 public abstract String getName();
508 public String getDbName() {
509 String lowerHyphen = this.getName();
511 lowerHyphen = namingException.getDBName(lowerHyphen);
516 public abstract ModelType getModelType();
518 public boolean hasChild(Introspector child) {
519 boolean result = false;
520 // check all inheriting types for this child
521 if ("true".equals(this.getMetadata(ObjectMetadata.ABSTRACT))) {
522 String[] inheritors = this.getMetadata(ObjectMetadata.INHERITORS).split(",");
523 for (String inheritor : inheritors) {
525 Introspector temp = this.loader.introspectorFromName(inheritor);
526 result = temp.hasProperty(child.getName());
530 } catch (AAIUnknownObjectException e) {
532 "Skipping inheritor " + inheritor + " (Unknown Object) " + LogFormatTools.getStackTop(e));
536 result = this.hasProperty(child.getName());
541 public void setURIChain(String uri) {
545 public abstract String getObjectId() throws UnsupportedEncodingException;
547 public String getURI() throws UnsupportedEncodingException {
548 // String result = this.uriChain;
550 String namespace = this.getMetadata(ObjectMetadata.NAMESPACE);
551 String container = this.getMetadata(ObjectMetadata.CONTAINER);
552 if (this.isContainer()) {
553 result += "/" + this.getName();
556 if (container != null) {
557 result += "/" + container;
559 result += "/" + this.getDbName() + "/" + this.findKey();
561 if (namespace != null && !namespace.equals("")) {
562 result = "/" + namespace + result;
569 public String getGenericURI() {
571 if (this.isContainer()) {
572 result += "/" + this.getName();
574 result += "/" + this.getDbName();
575 for (String key : this.getKeys()) {
576 result += "/{" + this.getDbName() + "-" + key + "}";
583 public String getFullGenericURI() {
585 String namespace = this.getMetadata(ObjectMetadata.NAMESPACE);
586 String container = this.getMetadata(ObjectMetadata.CONTAINER);
587 if (this.isContainer()) {
588 result += "/" + this.getName();
591 if (container != null) {
592 result += "/" + container;
594 result += "/" + this.getDbName();
596 for (String key : this.getKeys()) {
597 result += "/{" + this.getDbName() + "-" + key + "}";
599 if (namespace != null && !namespace.equals("")) {
600 result = "/" + namespace + result;
608 public abstract String preProcessKey(String key);
610 protected abstract String findKey() throws UnsupportedEncodingException;
612 public abstract String marshal(MarshallerProperties properties);
614 public abstract Object getUnderlyingObject();
616 public String marshal(boolean formatted) {
617 MarshallerProperties properties =
618 new MarshallerProperties.Builder(MediaType.APPLICATION_JSON_TYPE).formatted(formatted).build();
620 return marshal(properties);
623 public String makeSingular(String word) {
625 String result = word;
626 result = result.replaceAll("(?:([ho])es|s)$", "");
628 if (result.equals("ClassesOfService")) {
629 result = "ClassOfService";
630 } else if (result.equals("CvlanTag")) {
631 result = "CvlanTagEntry";
632 } else if (result.equals("Metadata")) {
633 result = "Metadatum";
638 protected String makePlural(String word) {
639 String result = word;
641 if (result.equals("cvlan-tag-entry")) {
643 } else if (result.equals("class-of-service")) {
644 return "classes-of-service";
645 } else if (result.equals("metadatum")) {
648 result = result.replaceAll("([a-z])$", "$1s");
649 result = result.replaceAll("([hox])s$", "$1es");
651 * if (result.equals("classes-of-services")) {
652 * result = "classes-of-service";
659 public abstract String getMetadata(ObjectMetadata metadataName);
661 public abstract Map<PropertyMetadata, String> getPropertyMetadata(String propName);
663 public Optional<String> getPropertyMetadata(String propName, PropertyMetadata metadataName) {
664 final String resultValue = this.getPropertyMetadata(propName).getOrDefault(metadataName, "");
665 Optional<String> result = Optional.empty();
667 if (!resultValue.isEmpty()) {
668 result = Optional.of(resultValue);
674 public abstract SchemaVersion getVersion();
676 public Loader getLoader() {
680 public boolean isTopLevel() {
682 return this.getMetadata(ObjectMetadata.NAMESPACE) != null;