aboutsummaryrefslogtreecommitdiffstats
path: root/feature-pooling-dmaap/src/main/java/org/onap/policy/drools/pooling/extractor/ClassExtractors.java
diff options
context:
space:
mode:
Diffstat (limited to 'feature-pooling-dmaap/src/main/java/org/onap/policy/drools/pooling/extractor/ClassExtractors.java')
-rw-r--r--feature-pooling-dmaap/src/main/java/org/onap/policy/drools/pooling/extractor/ClassExtractors.java466
1 files changed, 466 insertions, 0 deletions
diff --git a/feature-pooling-dmaap/src/main/java/org/onap/policy/drools/pooling/extractor/ClassExtractors.java b/feature-pooling-dmaap/src/main/java/org/onap/policy/drools/pooling/extractor/ClassExtractors.java
new file mode 100644
index 00000000..cb12a6ac
--- /dev/null
+++ b/feature-pooling-dmaap/src/main/java/org/onap/policy/drools/pooling/extractor/ClassExtractors.java
@@ -0,0 +1,466 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.drools.pooling.extractor;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.commons.lang.StringUtils;
+import org.onap.policy.drools.utils.Pair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Extractors for each object class. Properties define how the data is to be
+ * extracted for a given class, where the properties are similar to the
+ * following:
+ *
+ * <pre>
+ * <code>&lt;a.prefix>.&lt;class.name> = ${event.reqid}</code>
+ * </pre>
+ *
+ * If it doesn't find a property for the class, then it looks for a property for
+ * that class' super class or interfaces. Extractors are compiled and cached.
+ */
+public class ClassExtractors {
+
+ private static final Logger logger = LoggerFactory.getLogger(ClassExtractors.class);
+
+ /**
+ * Properties that specify how the data is to be extracted from a given
+ * class.
+ */
+ private final Properties properties;
+
+ /**
+ * Property prefix, including a trailing ".".
+ */
+ private final String prefix;
+
+ /**
+ * Type of item to be extracted.
+ */
+ private final String type;
+
+ /**
+ * Maps the class name to its extractor.
+ */
+ private final ConcurrentHashMap<String, Extractor> class2extractor = new ConcurrentHashMap<>();
+
+ /**
+ *
+ * @param props properties that specify how the data is to be extracted from
+ * a given class
+ * @param prefix property name prefix, prepended before the class name
+ * @param type type of item to be extracted
+ */
+ public ClassExtractors(Properties props, String prefix, String type) {
+ this.properties = props;
+ this.prefix = (prefix.endsWith(".") ? prefix : prefix + ".");
+ this.type = type;
+ }
+
+ /**
+ * Gets the number of extractors in the map.
+ *
+ * @return gets the number of extractors in the map
+ */
+ protected int size() {
+ return class2extractor.size();
+ }
+
+ /**
+ * Extracts the desired data item from an object.
+ *
+ * @param object object from which to extract the data item
+ * @return the extracted item, or {@code null} if it could not be extracted
+ */
+ public Object extract(Object object) {
+ if (object == null) {
+ return null;
+ }
+
+ Extractor ext = getExtractor(object);
+
+ return ext.extract(object);
+ }
+
+ /**
+ * Gets the extractor for the given type of object, creating one if it
+ * doesn't exist yet.
+ *
+ * @param object object whose extracted is desired
+ * @return an extractor for the object
+ */
+ private Extractor getExtractor(Object object) {
+ Class<?> clazz = object.getClass();
+ Extractor ext = class2extractor.get(clazz.getName());
+
+ if (ext == null) {
+ // allocate a new extractor, if another thread doesn't beat us to it
+ ext = class2extractor.computeIfAbsent(clazz.getName(), xxx -> buildExtractor(clazz));
+ }
+
+ return ext;
+ }
+
+ /**
+ * Builds an extractor for the class.
+ *
+ * @param clazz class for which the extractor should be built
+ *
+ * @return a new extractor
+ */
+ private Extractor buildExtractor(Class<?> clazz) {
+ String value = properties.getProperty(prefix + clazz.getName(), null);
+ if (value != null) {
+ // property has config info for this class - build the extractor
+ return buildExtractor(clazz, value);
+ }
+
+ /*
+ * Get the extractor, if any, for the super class or interfaces, but
+ * don't add one if it doesn't exist
+ */
+ Extractor ext = getClassExtractor(clazz, false);
+ if (ext != null) {
+ return ext;
+ }
+
+ /*
+ * No extractor defined for for this class or its super class - we
+ * cannot extract data items from objects of this type, so just
+ * allocated a null extractor.
+ */
+ logger.warn("missing property " + prefix + clazz.getName());
+ return new NullExtractor();
+ }
+
+ /**
+ * Builds an extractor for the class, based on the config value extracted
+ * from the corresponding property.
+ *
+ * @param clazz class for which the extractor should be built
+ * @param value config value (e.g., "${event.request.id}"
+ * @return a new extractor
+ */
+ private Extractor buildExtractor(Class<?> clazz, String value) {
+ if (!value.startsWith("${")) {
+ logger.warn("property value for " + prefix + clazz.getName() + " does not start with '${'");
+ return new NullExtractor();
+ }
+
+ if (!value.endsWith("}")) {
+ logger.warn("property value for " + prefix + clazz.getName() + " does not end with '}'");
+ return new NullExtractor();
+ }
+
+ // get the part in the middle
+ String val = value.substring(2, value.length() - 1);
+ if (val.startsWith(".")) {
+ logger.warn("property value for " + prefix + clazz.getName() + " begins with '.'");
+ return new NullExtractor();
+ }
+
+ if (val.endsWith(".")) {
+ logger.warn("property value for " + prefix + clazz.getName() + " ends with '.'");
+ return new NullExtractor();
+ }
+
+ // everything's valid - create the extractor
+ try {
+ ComponetizedExtractor ext = new ComponetizedExtractor(clazz, val.split("[.]"));
+
+ /*
+ * If there's only one extractor, then just return it, otherwise
+ * return the whole extractor.
+ */
+ return (ext.extractors.length == 1 ? ext.extractors[0] : ext);
+
+ } catch (ExtractorException e) {
+ logger.warn("cannot build extractor for " + clazz.getName());
+ return new NullExtractor();
+ }
+ }
+
+ /**
+ * Gets the extractor for a class, examining all super classes and
+ * interfaces.
+ *
+ * @param clazz class whose extractor is desired
+ * @param addOk {@code true} if the extractor may be added, provided the
+ * property is defined, {@code false} otherwise
+ * @return the extractor to be used for the class, or {@code null} if no
+ * extractor has been defined yet
+ */
+ private Extractor getClassExtractor(Class<?> clazz, boolean addOk) {
+ if (clazz == null) {
+ return null;
+ }
+
+ Extractor ext = null;
+
+ if (addOk) {
+ String val = properties.getProperty(prefix + clazz.getName(), null);
+
+ if (val != null) {
+ /*
+ * A property is defined for this class, so create the extractor
+ * for it.
+ */
+ return class2extractor.computeIfAbsent(clazz.getName(), xxx -> buildExtractor(clazz));
+ }
+ }
+
+ // see if the superclass has an extractor
+ if ((ext = getClassExtractor(clazz.getSuperclass(), true)) != null) {
+ return ext;
+ }
+
+ // check the interfaces, too
+ for (Class<?> clz : clazz.getInterfaces()) {
+ if ((ext = getClassExtractor(clz, true)) != null) {
+ break;
+ }
+ }
+
+ return ext;
+ }
+
+ /**
+ * Extractor that always returns {@code null}. Used when no extractor could
+ * be built for a given object type.
+ */
+ private class NullExtractor implements Extractor {
+
+ @Override
+ public Object extract(Object object) {
+ logger.warn("cannot extract " + type + " from " + object.getClass());
+ return null;
+ }
+ }
+
+ /**
+ * Component-ized extractor. Extracts an object that is referenced
+ * hierarchically, where each name identifies a particular component within
+ * the hierarchy. Supports retrieval from {@link Map} objects, as well as
+ * via getXxx() methods, or by direct field retrieval.
+ * <p>
+ * Note: this will <i>not</i> work if POJOs are contained within a Map.
+ */
+ private class ComponetizedExtractor implements Extractor {
+
+ /**
+ * Extractor for each component.
+ */
+ private final Extractor[] extractors;
+
+ /**
+ *
+ * @param clazz the class associated with the object at the root of the
+ * hierarchy
+ * @param names name associated with each component
+ * @throws ExtractorException
+ */
+ public ComponetizedExtractor(Class<?> clazz, String[] names) throws ExtractorException {
+ this.extractors = new Extractor[names.length];
+
+ Class<?> clz = clazz;
+
+ for (int x = 0; x < names.length; ++x) {
+ String comp = names[x];
+
+ Pair<Extractor, Class<?>> pair = buildExtractor(clz, comp);
+
+ extractors[x] = pair.first();
+ clz = pair.second();
+ }
+ }
+
+ /**
+ * Builds an extractor for the given component of an object.
+ *
+ * @param clazz type of object from which the component will be
+ * extracted
+ * @param comp name of the component to extract
+ * @return a pair containing the extractor and the extracted object's
+ * type
+ * @throws ExtractorException
+ */
+ private Pair<Extractor, Class<?>> buildExtractor(Class<?> clazz, String comp) throws ExtractorException {
+ Pair<Extractor, Class<?>> pair = null;
+
+ if (pair == null) {
+ pair = getMethodExtractor(clazz, comp);
+ }
+
+ if (pair == null) {
+ pair = getFieldExtractor(clazz, comp);
+ }
+
+ if (pair == null) {
+ pair = getMapExtractor(clazz, comp);
+ }
+
+
+ // didn't find an extractor
+ if (pair == null) {
+ throw new ExtractorException("class " + clazz + " contains no element " + comp);
+ }
+
+ return pair;
+ }
+
+ @Override
+ public Object extract(Object object) {
+ Object obj = object;
+
+ for (Extractor ext : extractors) {
+ if (obj == null) {
+ break;
+ }
+
+ obj = ext.extract(obj);
+ }
+
+ return obj;
+ }
+
+ /**
+ * Gets an extractor that invokes a getXxx() method to retrieve the
+ * object.
+ *
+ * @param clazz container's class
+ * @param name name of the property to be retrieved
+ * @return a new extractor, or {@code null} if the class does not
+ * contain the corresponding getXxx() method
+ * @throws ExtractorException if the getXxx() method is inaccessible
+ */
+ private Pair<Extractor, Class<?>> getMethodExtractor(Class<?> clazz, String name) throws ExtractorException {
+ Method meth;
+
+ String nm = "get" + StringUtils.capitalize(name);
+
+ try {
+ meth = clazz.getMethod(nm);
+
+ Class<?> retType = meth.getReturnType();
+ if (retType == void.class) {
+ // it's a void method, thus it won't return an object
+ return null;
+ }
+
+ return new Pair<>(new MethodExtractor(meth), retType);
+
+ } catch (NoSuchMethodException expected) {
+ // no getXxx() method, maybe there's a field by this name
+ return null;
+
+ } catch (SecurityException e) {
+ throw new ExtractorException("inaccessible method " + clazz + "." + nm, e);
+ }
+ }
+
+ /**
+ * Gets an extractor for a field within the object.
+ *
+ * @param clazz container's class
+ * @param name name of the field whose value is to be extracted
+ * @return a new extractor, or {@code null} if the class does not
+ * contain the given field
+ * @throws ExtractorException if the field is inaccessible
+ */
+ private Pair<Extractor, Class<?>> getFieldExtractor(Class<?> clazz, String name) throws ExtractorException {
+
+ Field field = getClassField(clazz, name);
+ if (field == null) {
+ return null;
+ }
+
+ return new Pair<>(new FieldExtractor(field), field.getType());
+ }
+
+ /**
+ * Gets an extractor for an item within a Map object.
+ *
+ * @param clazz container's class
+ * @param key item key within the map
+ * @return a new extractor, or {@code null} if the class is not a Map
+ * subclass
+ * @throws ExtractorException
+ */
+ private Pair<Extractor, Class<?>> getMapExtractor(Class<?> clazz, String key) throws ExtractorException {
+
+ if (!Map.class.isAssignableFrom(clazz)) {
+ return null;
+ }
+
+ /*
+ * Don't know the value's actual type, so we'll assume it's a Map
+ * for now. Things should still work OK, as this is only used to
+ * direct the constructor on what type of extractor to create next.
+ * If the object turns out not to be a map, then the MapExtractor
+ * for the next component will just return null.
+ */
+ return new Pair<>(new MapExtractor(key), Map.class);
+ }
+
+ /**
+ * Gets field within a class, examining all super classes and
+ * interfaces.
+ *
+ * @param clazz class whose field is desired
+ * @param name name of the desired field
+ * @return the field within the class, or {@code null} if the field does
+ * not exist
+ * @throws ExtractorException if the field is inaccessible
+ */
+ private Field getClassField(Class<?> clazz, String name) throws ExtractorException {
+ if (clazz == null) {
+ return null;
+ }
+
+ try {
+ return clazz.getDeclaredField(name);
+
+ } catch (NoSuchFieldException expected) {
+ // no field by this name - try super class & interfaces
+
+ } catch (SecurityException e) {
+ throw new ExtractorException("inaccessible field " + clazz + "." + name, e);
+ }
+
+
+ Field field;
+
+ // see if the superclass has an extractor
+ if ((field = getClassField(clazz.getSuperclass(), name)) != null) {
+ return field;
+ }
+
+ // not necessary to check the interfaces
+
+ return field;
+ }
+ }
+}