All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.mx.path.gateway.configuration.ConfigurationBinder Maven / Gradle / Ivy

There is a newer version: 4.2.0
Show newest version
package com.mx.path.gateway.configuration;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.google.common.base.Supplier;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.mx.path.core.common.collection.ObjectArray;
import com.mx.path.core.common.collection.ObjectMap;
import com.mx.path.core.common.configuration.ConfigurationField;
import com.mx.path.core.common.lang.Strings;
import com.mx.path.core.common.reflection.Annotations;
import com.mx.path.core.common.reflection.Constructors;
import com.mx.path.core.common.reflection.Fields;
import com.mx.path.core.common.serialization.ConfigurationTypeAdapter;
import com.mx.path.core.utility.reflection.ClassHelper;
import com.mx.path.gateway.configuration.annotations.ClientID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Build and bind configuration POJO from ObjectMap
 *
 * 

The POJO must have a no-argument constructor. Fields to be populated must * be annotated with {@link ConfigurationField} */ public class ConfigurationBinder { private static final Logger LOGGER = LoggerFactory.getLogger(GatewayObjectConfigurator.class); private static final Gson GSON = new GsonBuilder() .setPrettyPrinting() .registerTypeAdapterFactory(new ConfigurationTypeAdapter.Factory()) .create(); private final ConfigurationState state; private final String clientId; public ConfigurationBinder(String clientId, ConfigurationState state) { this.clientId = clientId; this.state = state; } /** * Create an instance of klass and populate its fields from configurationMap * * @param klass * @param configurationMap * @return */ public T build(Class klass, ObjectMap configurationMap) { T configuration = Constructors.instantiateWithNoArgumentConstructor(klass); configure(configuration, configurationMap); return configuration; } /** * Populate fields of given configuration POJO with given {@link ObjectMap} * * @param configuration Configuration POJO instance * @param configurationMap Configuration ObjectMap */ public final void configure(Object configuration, ObjectMap configurationMap) { populateFields(configuration, configurationMap); if (Configurable.class.isAssignableFrom(configuration.getClass())) { ((Configurable) configuration).initialize(); ((Configurable) configuration).validate(state); } try { LOGGER.debug("Configuration binding: " + configuration.getClass().getName() + " -> " + GSON.toJson(configuration)); } catch (Exception e) { LOGGER.warn("Unable to serialize configuration: " + configuration.getClass().getName(), e); } } /** * Constructs array for given configuration map * * @param configurationValue * @param annotatedField * @param inArray * @return */ private List buildArray(ObjectArray configurationValue, Annotations.AnnotatedField annotatedField, boolean inArray) { List array = new ArrayList<>(); annotatedField.setElementType(resolveArrayElementClass(annotatedField)); if (annotatedField.getElementType() == null) { throw new ConfigurationError("Must provide elementType if configuration element is a list", state); } return buildInArray(inArray, annotatedField.getField().getName(), () -> { int objectIndex = 0; for (Object item : configurationValue) { state.pushLevel(String.valueOf(objectIndex++)); try { array.add(buildValue(item, annotatedField, true)); } finally { state.popLevel(); } } return array; }); } /** * Pushes and pops given level around executing supplier when populating an array * * @param inArray * @param level * @param supplier * @param * @return */ private T buildInArray(boolean inArray, String level, Supplier supplier) { if (!inArray) { state.pushLevel(level); } try { return supplier.get(); } finally { if (!inArray) { state.popLevel(); } } } /** * Construct Object for given configuration map * * @param configurationMap * @param annotatedField * @param inArray * @return */ private Object buildObject(ObjectMap configurationMap, Annotations.AnnotatedField annotatedField, boolean inArray) { return buildInArray(inArray, annotatedField.getField().getName(), () -> { Class klass = annotatedField.getElementType() != null ? annotatedField.getElementType() : annotatedField.getAnnotation().elementType(); if (klass == Void.class) { klass = annotatedField.getField().getType(); } return build(klass, configurationMap); }); } /** * Coerces given, raw value from configuration map to expected type * * @param configurationValue * @param annotatedField * @return */ private Object buildValue(Object configurationValue, Annotations.AnnotatedField annotatedField) { return buildValue(configurationValue, annotatedField, false); } /** * Coerces given, raw value from configuration map to expected type. * * @param configurationValue * @param annotatedField * @param inArray * @return */ private Object buildValue(Object configurationValue, Annotations.AnnotatedField annotatedField, boolean inArray) { if (configurationValue instanceof ObjectArray) { return buildArray((ObjectArray) configurationValue, annotatedField, inArray); } else if (configurationValue instanceof ObjectMap) { return buildObject((ObjectMap) configurationValue, annotatedField, inArray); } else if (annotatedField.getElementType() != null) { return Fields.coerceValueType(annotatedField.getElementType(), configurationValue); } else { return configurationValue; } } /** * Set all {@link ConfigurationField} field values from configurationMap * * @param obj * @param configurationMap */ private void populateFields(Object obj, ObjectMap configurationMap) { Map> configurationFieldMap = prepareConfigurationObjectFields(obj); if (configurationMap == null) { configurationMap = new ObjectMap(); } configurationMap.forEach((fieldName, fieldValue) -> { Annotations.AnnotatedField configurationAnnotatedField = configurationFieldMap.get(fieldName); state.withField(fieldName, () -> { Object value; if (configurationAnnotatedField == null) { throw new ConfigurationError("Unknown field", state); } Field field = configurationAnnotatedField.getField(); if (field.getType() == HashMap.class) { value = fieldValue; } else { value = this.buildValue(fieldValue, configurationAnnotatedField); } if (value != null) { Fields.setFieldValue(field, obj, value); } }); }); validate(obj); // Populate clientId fields List> clientIdFields = Annotations.fieldsWithAnnotation(ClientID.class, obj.getClass()); clientIdFields.forEach(annotatedField -> { String fieldName = annotatedField.getField().getName(); Field field = annotatedField.getField(); state.withField(fieldName, () -> { Fields.setFieldValue(field, obj, clientId); }); }); } /** * @param obj * @return Map of fields. Key = expected name of field, Value = AnnotatedField object */ private Map> prepareConfigurationObjectFields(Object obj) { List annotatedFields = Annotations.fieldsAndAnnotations(obj.getClass()); return annotatedFields.stream() .map((field) -> field.asAnnotatedField(ConfigurationField.class)) .collect(Collectors.toMap(field -> { if (field.getAnnotation() != null && Strings.isNotBlank(field.getAnnotation().value())) { return field.getAnnotation().value(); } else { return field.getField().getName(); } }, field -> field)); } /** * @param annotatedField * @return the element class of given array field */ private Class resolveArrayElementClass(Annotations.AnnotatedField annotatedField) { if (annotatedField.getAnnotation() != null && annotatedField.getAnnotation().elementType() != Void.class) { return annotatedField.getAnnotation().elementType(); } else { List klasses = new ClassHelper().resolveParameterizedFieldTypes(annotatedField.getField()); if (klasses.size() == 1) { return (Class) klasses.get(0); } } return null; } /** * Apply annotation validations to given object * * @param obj */ private void validate(Object obj) { List> annotatedFields = Annotations.fieldsWithAnnotation(ConfigurationField.class, obj.getClass()); annotatedFields.forEach(annotatedField -> { Object value = Fields.getFieldValue(annotatedField.getField(), obj); String field = Strings.isBlank(annotatedField.getAnnotation().value()) ? annotatedField.getField().getName() : annotatedField.getAnnotation().value(); state.withField(field, () -> validateField(annotatedField, value)); }); } /** * Apply annotation validations to given field value * * @param annotatedField * @param value */ private void validateField(Annotations.AnnotatedField annotatedField, Object value) { if (annotatedField.getAnnotation() == null) { return; } if (annotatedField.getAnnotation().required()) { if (value == null) { throw new ConfigurationError("Value required", state); } if (value instanceof String && Strings.isBlank((String) value)) { throw new ConfigurationError("Value required", state); } } } }