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 org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
25 import com.google.common.base.CaseFormat;
26 import org.apache.commons.lang.ClassUtils;
27 import org.eclipse.persistence.exceptions.DynamicException;
28 import org.onap.aai.config.SpringContextAware;
29 import org.onap.aai.introspection.exceptions.AAIUnknownObjectException;
30 import org.onap.aai.logging.ErrorLogHelper;
31 import org.onap.aai.logging.LogFormatTools;
32 import org.onap.aai.nodes.CaseFormatStore;
33 import org.onap.aai.nodes.NodeIngestor;
34 import org.onap.aai.restcore.MediaType;
35 import org.onap.aai.schema.enums.ObjectMetadata;
36 import org.onap.aai.schema.enums.PropertyMetadata;
37 import org.onap.aai.setup.SchemaVersion;
38 import org.onap.aai.workarounds.NamingExceptions;
40 import java.io.UnsupportedEncodingException;
41 import java.lang.reflect.InvocationTargetException;
43 import java.util.stream.Collectors;
45 public abstract class Introspector implements Cloneable {
47 private static final Logger LOGGER = LoggerFactory.getLogger(Introspector.class);
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 private Set<String> dslStartNodeProperties = null;
58 protected CaseFormatStore caseFormatStore = null;
59 protected NodeIngestor nodeIngestor;
61 protected Introspector(Object obj) {
62 this.nodeIngestor = SpringContextAware.getBean(NodeIngestor.class);
63 this.caseFormatStore = nodeIngestor.getCaseFormatStore();
66 public abstract boolean hasProperty(String name);
68 protected String convertPropertyName(String name) {
69 return caseFormatStore.fromLowerHyphenToLowerCamel(name).orElseGet(() -> {
70 LOGGER.debug("Unable to find {} in the store from lower hyphen to lower camel", name);
71 return CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL, name);
75 protected abstract Object get(String name);
77 protected abstract void set(String name, Object value);
81 * @param name the property name you'd like to retrieve the value for
82 * @return the value of the property
84 public <T> T getValue(String name) {
85 String convertedName = convertPropertyName(name);
88 if (this.hasProperty(name)) {
89 result = this.get(convertedName);
91 /* property not found - slightly ambiguous */
95 Class<?> clazz = this.getClass(name);
96 if (this.isListType(name) && result == null) {
98 this.set(convertedName, clazz.newInstance());
99 result = this.get(convertedName);
100 } catch (DynamicException | InstantiationException | IllegalAccessException e) {
101 LOGGER.warn(e.getMessage(), e);
108 public Introspector getWrappedValue(String name) {
109 String convertedName = convertPropertyName(name);
112 if (this.hasProperty(name)) {
113 value = this.get(convertedName);
115 /* property not found - slightly ambiguous */
119 Class<?> clazz = this.getClass(name);
120 if (this.isListType(name) && value == null) {
122 this.set(convertedName, clazz.newInstance());
123 value = this.get(convertedName);
124 } catch (DynamicException | InstantiationException | IllegalAccessException e) {
125 LOGGER.warn(e.getMessage(), e);
129 return IntrospectorFactory.newInstance(this.getModelType(), value);
137 public List<Introspector> getWrappedListValue(String name) {
138 String convertedName = convertPropertyName(name);
140 List<Introspector> resultList = new ArrayList<>();
141 if (this.hasProperty(name)) {
142 value = this.get(convertedName);
144 /* property not found - slightly ambiguous */
147 boolean isListType = this.isListType(name);
148 if (!this.isListType(name)) {
151 Class<?> clazz = this.getClass(name);
152 if (isListType && value == null) {
154 this.set(convertedName, clazz.newInstance());
155 value = this.get(convertedName);
156 } catch (DynamicException | InstantiationException | IllegalAccessException e) {
157 LOGGER.warn(e.getMessage(), e);
161 List<Object> valueList = (List<Object>) value;
163 for (Object item : valueList) {
164 resultList.add(IntrospectorFactory.newInstance(this.getModelType(), item));
171 public Object castValueAccordingToSchema(String name, Object obj) {
173 Class<?> nameClass = this.getClass(name);
174 if (nameClass == null) {
175 throw new IllegalArgumentException("property: " + name + " does not exist on " + this.getDbName());
180 if (!obj.getClass().getName().equals(nameClass.getName())) {
181 if (nameClass.isPrimitive()) {
182 nameClass = ClassUtils.primitiveToWrapper(nameClass);
183 result = nameClass.getConstructor(String.class).newInstance(obj.toString());
185 if (obj instanceof String) {
186 result = nameClass.getConstructor(String.class).newInstance(obj);
187 } else if (!this.isListType(name) && !this.isComplexType(name)) {
188 // box = obj.toString();
189 result = nameClass.getConstructor(String.class).newInstance(obj.toString());
192 } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
193 | InvocationTargetException | NoSuchMethodException | SecurityException e) {
194 ErrorLogHelper.logError("AAI_4017", e.getMessage());
200 public List<Object> castValueAccordingToSchema(String name, List<?> objs) {
201 List<Object> result = new ArrayList<>();
203 for (Object item : objs) {
204 result.add(this.castValueAccordingToSchema(name, item));
213 * @param name the property name you'd like to set the value of
214 * @param obj the value to be set
217 public void setValue(String name, Object obj) {
218 Object box = this.castValueAccordingToSchema(name, obj);
220 name = convertPropertyName(name);
226 * @return a list of all the properties available on the object
228 public abstract Set<String> getProperties();
230 public Set<String> getProperties(PropertyPredicate<Introspector, String> p) {
231 final Set<String> temp = new LinkedHashSet<>();
232 this.getProperties().stream().filter(item -> p.test(this, item)).forEach(temp::add);
233 return Collections.unmodifiableSet(temp);
236 public Set<String> getSimpleProperties(PropertyPredicate<Introspector, String> p) {
237 return this.getProperties().stream().filter(item -> p.test(this, item)).filter(this::isSimpleType)
238 .collect(Collectors.toSet());
243 * @return a list of the required properties on the object
245 public abstract Set<String> getRequiredProperties();
249 * @return a list of the properties that can be used to query the object in the db
251 public abstract Set<String> getKeys();
255 * @return a list of the all key properties for this object
257 public Set<String> getAllKeys() {
258 Set<String> result = null;
259 if (this.allKeys == null) {
260 Set<String> keys = this.getKeys();
261 result = new LinkedHashSet<>();
263 String altKeys = this.getMetadata(ObjectMetadata.ALTERNATE_KEYS_1);
264 if (altKeys != null) {
265 String[] altKeysArray = altKeys.split(",");
266 for (String altKey : altKeysArray) {
270 result = Collections.unmodifiableSet(result);
271 this.allKeys = result;
273 result = this.allKeys;
277 public Set<String> getIndexedProperties() {
278 Set<String> result = null;
280 if (this.indexedProperties == null) {
281 result = new LinkedHashSet<>();
282 Set<String> keys = this.getKeys();
284 String altKeys = this.getMetadata(ObjectMetadata.INDEXED_PROPS);
285 if (altKeys != null) {
286 String[] altKeysArray = altKeys.split(",");
287 for (String altKey : altKeysArray) {
291 this.indexedProperties = Collections.unmodifiableSet(result);
293 result = this.indexedProperties;
297 public Set<String> getDslStartNodeProperties() {
298 Set<String> result = null;
300 if (this.dslStartNodeProperties == null) {
302 * The dslStartNodeProperties will have keys by default
303 * If dslStartNodeProps exist in the oxm use it
304 * if not use the indexedProps
306 result = new LinkedHashSet<>(this.getKeys());
308 String dslKeys = this.getMetadata(ObjectMetadata.DSL_START_NODE_PROPS);
309 String indexedKeys = this.getMetadata(ObjectMetadata.INDEXED_PROPS);
310 if (dslKeys != null) {
311 Arrays.stream(dslKeys.split(",")).forEach(result::add);
313 else if(indexedKeys != null){
314 Arrays.stream(indexedKeys.split(",")).forEach(result::add);
316 this.dslStartNodeProperties = Collections.unmodifiableSet(result);
318 result = this.dslStartNodeProperties;
322 public Set<String> getUniqueProperties() {
323 Set<String> result = null;
324 if (this.uniqueProperties == null) {
325 String altKeys = this.getMetadata(ObjectMetadata.UNIQUE_PROPS);
326 result = new LinkedHashSet<>();
327 if (altKeys != null) {
328 String[] altKeysArray = altKeys.split(",");
329 for (String altKey : altKeysArray) {
333 this.uniqueProperties = Collections.unmodifiableSet(result);
336 result = this.uniqueProperties;
340 public Set<String> getDependentOn() {
341 String dependentOn = this.getMetadata(ObjectMetadata.DEPENDENT_ON);
342 if (dependentOn == null) {
343 return new LinkedHashSet<>();
345 return new LinkedHashSet<>(Arrays.asList(dependentOn.split(",")));
351 * @return the string name of the java class of the named property
353 public String getType(String name) {
354 Class<?> resultClass = this.getClass(name);
357 if (resultClass != null) {
358 result = resultClass.getName();
359 if (result.equals("java.util.ArrayList")) {
360 result = "java.util.List";
368 * This will returned the generic parameterized type of the underlying
369 * object if it exists
372 * @return the generic type of the java class of the underlying object
374 public String getGenericType(String name) {
375 Class<?> resultClass = this.getGenericTypeClass(name);
378 if (resultClass != null) {
379 result = resultClass.getName();
387 * @return the string name of the java class of the underlying object
389 public abstract String getJavaClassName();
393 * @param name the property name
394 * @return the Class object
396 public abstract Class<?> getClass(String name);
398 public abstract Class<?> getGenericTypeClass(String name);
402 * @param name the property name
403 * @return a new instance of the underlying type of this property
404 * @throws AAIUnknownObjectException
406 public Object newInstanceOfProperty(String name) throws AAIUnknownObjectException {
407 String type = this.getType(name);
408 return loader.objectFromName(type);
411 public Object newInstanceOfNestedProperty(String name) throws AAIUnknownObjectException {
412 String type = this.getGenericType(name);
413 return loader.objectFromName(type);
416 public Introspector newIntrospectorInstanceOfProperty(String name) throws AAIUnknownObjectException {
418 Introspector result = IntrospectorFactory.newInstance(this.getModelType(), this.newInstanceOfProperty(name));
424 public Introspector newIntrospectorInstanceOfNestedProperty(String name) throws AAIUnknownObjectException {
426 Introspector result =
427 IntrospectorFactory.newInstance(this.getModelType(), this.newInstanceOfNestedProperty(name));
434 * Is this type not a Java String or primitive
439 public boolean isComplexType(String name) {
440 String result = this.getType(name);
442 if (result.contains("aai") || result.equals("java.lang.Object")) {
449 public boolean isComplexGenericType(String name) {
450 String result = this.getGenericType(name);
452 if (result.contains("aai")) {
459 public boolean isSimpleType(String name) {
460 return !(this.isComplexType(name) || this.isListType(name));
463 public boolean isSimpleGenericType(String name) {
464 return !this.isComplexGenericType(name);
467 public boolean isListType(String name) {
468 String result = this.getType(name);
470 if (result.contains("java.util.List")) {
477 public boolean isContainer() {
478 Set<String> props = this.getProperties();
479 boolean result = false;
480 if (props.size() == 1 && this.isListType(props.iterator().next())) {
487 public abstract String getChildName();
489 public String getChildDBName() {
490 String result = this.getChildName();
492 result = namingException.getDBName(result);
496 public abstract String getName();
498 public String getDbName() {
499 String lowerHyphen = this.getName();
501 lowerHyphen = namingException.getDBName(lowerHyphen);
506 public abstract ModelType getModelType();
508 public boolean hasChild(Introspector child) {
509 boolean result = false;
510 // check all inheriting types for this child
511 if ("true".equals(this.getMetadata(ObjectMetadata.ABSTRACT))) {
512 String[] inheritors = this.getMetadata(ObjectMetadata.INHERITORS).split(",");
513 for (String inheritor : inheritors) {
515 Introspector temp = this.loader.introspectorFromName(inheritor);
516 result = temp.hasProperty(child.getName());
520 } catch (AAIUnknownObjectException e) {
522 "Skipping inheritor " + inheritor + " (Unknown Object) " + LogFormatTools.getStackTop(e));
526 result = this.hasProperty(child.getName());
531 public void setURIChain(String uri) {
535 public abstract String getObjectId() throws UnsupportedEncodingException;
537 public String getURI() throws UnsupportedEncodingException {
538 // String result = this.uriChain;
540 String namespace = this.getMetadata(ObjectMetadata.NAMESPACE);
541 String container = this.getMetadata(ObjectMetadata.CONTAINER);
542 if (this.isContainer()) {
543 result += "/" + this.getName();
546 if (container != null) {
547 result += "/" + container;
549 result += "/" + this.getDbName() + "/" + this.findKey();
551 if (namespace != null && !namespace.equals("")) {
552 result = "/" + namespace + result;
559 public String getGenericURI() {
561 if (this.isContainer()) {
562 result += "/" + this.getName();
564 result += "/" + this.getDbName();
565 for (String key : this.getKeys()) {
566 result += "/{" + this.getDbName() + "-" + key + "}";
573 public String getFullGenericURI() {
575 String namespace = this.getMetadata(ObjectMetadata.NAMESPACE);
576 String container = this.getMetadata(ObjectMetadata.CONTAINER);
577 if (this.isContainer()) {
578 result += "/" + this.getName();
581 if (container != null) {
582 result += "/" + container;
584 result += "/" + this.getDbName();
586 for (String key : this.getKeys()) {
587 result += "/{" + this.getDbName() + "-" + key + "}";
589 if (namespace != null && !namespace.equals("")) {
590 result = "/" + namespace + result;
598 public abstract String preProcessKey(String key);
600 protected abstract String findKey() throws UnsupportedEncodingException;
602 public abstract String marshal(MarshallerProperties properties);
604 public abstract Object getUnderlyingObject();
606 public String marshal(boolean formatted) {
607 MarshallerProperties properties =
608 new MarshallerProperties.Builder(MediaType.APPLICATION_JSON_TYPE).formatted(formatted).build();
610 return marshal(properties);
613 public String makeSingular(String word) {
615 String result = word;
616 result = result.replaceAll("(?:([ho])es|s)$", "");
618 if (result.equals("ClassesOfService")) {
619 result = "ClassOfService";
620 } else if (result.equals("CvlanTag")) {
621 result = "CvlanTagEntry";
622 } else if (result.equals("Metadata")) {
623 result = "Metadatum";
628 protected String makePlural(String word) {
629 String result = word;
631 if (result.equals("cvlan-tag-entry")) {
633 } else if (result.equals("class-of-service")) {
634 return "classes-of-service";
635 } else if (result.equals("metadatum")) {
638 result = result.replaceAll("([a-z])$", "$1s");
639 result = result.replaceAll("([hox])s$", "$1es");
641 * if (result.equals("classes-of-services")) {
642 * result = "classes-of-service";
649 public abstract String getMetadata(ObjectMetadata metadataName);
651 public abstract Map<PropertyMetadata, String> getPropertyMetadata(String propName);
653 public Optional<String> getPropertyMetadata(String propName, PropertyMetadata metadataName) {
654 final String resultValue = this.getPropertyMetadata(propName).getOrDefault(metadataName, "");
655 Optional<String> result = Optional.empty();
657 if (!resultValue.isEmpty()) {
658 result = Optional.of(resultValue);
664 public abstract SchemaVersion getVersion();
666 public Loader getLoader() {
670 public boolean isTopLevel() {
672 return this.getMetadata(ObjectMetadata.NAMESPACE) != null;