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

org.demoiselle.jee.configuration.ConfigurationLoader Maven / Gradle / Ivy

Go to download

Demoiselle Configuration habilita os projetos a usarem configurações em arquivos .properties, .xml ou variáveis de ambiente.

There is a newer version: 3.0.4
Show newest version
/*
 * Demoiselle Framework
 *
 * License: GNU Lesser General Public License (LGPL), version 3 or later.
 * See the lgpl.txt file in the root directory or .
 */
package org.demoiselle.jee.configuration;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.AmbiguousResolutionException;
import javax.enterprise.inject.spi.CDI;
import javax.inject.Inject;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

import org.apache.commons.configuration2.Configuration;
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.SystemConfiguration;
import org.apache.commons.configuration2.XMLConfiguration;
import org.apache.commons.configuration2.builder.BasicConfigurationBuilder;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Parameters;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.demoiselle.jee.configuration.annotation.SuppressConfigurationLogger;
import org.demoiselle.jee.configuration.exception.DemoiselleConfigurationException;
import org.demoiselle.jee.configuration.exception.DemoiselleConfigurationValueExtractorException;
import org.demoiselle.jee.configuration.extractor.ConfigurationValueExtractor;
import org.demoiselle.jee.configuration.extractor.impl.ConfigurationInternalDemoiselleValueExtractor;
import org.demoiselle.jee.configuration.message.ConfigurationMessage;
import org.demoiselle.jee.core.annotation.Ignore;
import org.demoiselle.jee.core.annotation.Name;

/**
 *
 * Class responsible for managing the source of a data extraction, identified
 * which fields to be filled, find the extractor for each field type, fill and
 * validate the data field.
 *
 * @author SERPRO
 *
 */
@ApplicationScoped
public class ConfigurationLoader implements Serializable {

	private static final long serialVersionUID = 1L;

	@Inject
	private ConfigurationMessage message;

	// @Inject
	// private DemoiselleMessage demoiselleMessage;

	private static final Logger logger = Logger.getLogger(ConfigurationLoader.class.getName());

	// Object annotated with @Configuration
	private transient Object targetObject;

	// Class type of object annotated with @Configuration
	private Class targetBaseClass;

	// Type of source
	private ConfigurationType configurationType;

	private String resource;

	private String prefix;

	// Apache configuration
	private transient List configurations = null;

	// Fields of object annotated with @Configuration
	private transient List fields;

	private transient Map loadedCache = null;

	@PostConstruct
	private void init() {
		configurations = new ArrayList<>();
		loadedCache = new ConcurrentHashMap<>();
	}

	/**
	 * 

* Processes the annotated class with {@link Configuration}. *

* *

* After the first class configuration procedure is added to the cache to * avoid repeated processing. *

* * @param object * Object annotated with {@link Configuration} to be populated * @param baseClass * Class type to be populated * @throws DemoiselleConfigurationException * When there is a problem in the process */ public void load(final Object object, Class baseClass) { Boolean isLoaded = loadedCache.get(object); if (isLoaded == null || !isLoaded) { try { processConfiguration(object, baseClass); loadedCache.put(object, true); } catch (DemoiselleConfigurationException c) { loadedCache.put(object, false); throw c; } } } /** * * Load values from the selected source and fill object annotated * with @Configuration. * * This method has the engine to process the configuration feature. * * @param targetObject * The object to fill * @param targetBaseClass * The class type of object to fill * @throws DemoiselleConfigurationException */ private void processConfiguration(final Object targetObject, Class targetBaseClass) { logger.info("*******************************************************"); // logger.info(demoiselleMessage.frameworkName() + " " + // demoiselleMessage.version()); // logger.info(message.startMessage()); logger.info(message.loadConfigurationClass(targetBaseClass.getName())); this.targetObject = targetObject; this.targetBaseClass = targetBaseClass; loadFieldsFromTargetObject(); validateFieldsFromTargetObject(); identifyConfigurationType(); identifyResourceName(); loadConfigurationType(); if (configurations != null && !configurations.isEmpty()) { identifyPrefix(); fillTargetObjectWithValues(); } // Validate values using JavaBeans Validation validateValues(); printConfiguration(); } /** * Log all configurations */ private void printConfiguration() { Boolean suppressAllFields = hasSuppressLogger(); fields.forEach(field -> { Object obj = getFieldValueFromObject(field, targetObject); String strMessage = message.configurationFieldLoaded(prefix + getKey(field), obj); // Check if the field has @SuppressLogger if (suppressAllFields || hasSuppressLogger(field)) { strMessage = message.configurationFieldSuppress(prefix + getKey(field), SuppressConfigurationLogger.class.getSimpleName()); } logger.info(strMessage); }); } private Boolean hasSuppressLogger() { return targetObject.getClass().getAnnotation(SuppressConfigurationLogger.class) == null ? Boolean.FALSE : Boolean.TRUE; } private Boolean hasSuppressLogger(Field field) { return field.getAnnotation(SuppressConfigurationLogger.class) == null ? Boolean.FALSE : Boolean.TRUE; } private void loadFieldsFromTargetObject() { fields = getNonStaticFields(targetBaseClass); } private void validateFieldsFromTargetObject() { fields.forEach(this::validateField); } /** * Check if the field param has {@value Name} annotation, if it has, * validate if the value is filled * * @param field * Current field */ private void validateField(Field field) { Name annotation = field.getAnnotation(Name.class); if (annotation != null && annotation.value().isEmpty()) { throw new DemoiselleConfigurationException( message.configurationNameAttributeCantBeEmpty(Name.class.getSimpleName()), new IllegalArgumentException()); } } private void identifyConfigurationType() { configurationType = targetBaseClass .getAnnotation(org.demoiselle.jee.configuration.annotation.Configuration.class).type(); } /** * Load the name of resource that contains the values to fill object. Unless * with the type of configuration is SYSTEM type. */ private void identifyResourceName() { if (configurationType != ConfigurationType.SYSTEM) { String name = targetBaseClass.getAnnotation(org.demoiselle.jee.configuration.annotation.Configuration.class) .resource(); String extension = configurationType.toString().toLowerCase(); resource = name + "." + extension; } } /** * Identify and create Apache Configuration based on type informed on * {@link ConfigurationType} * */ private void loadConfigurationType() { BasicConfigurationBuilder builder = createConfiguration(); configurations.clear(); try { if (builder instanceof FileBasedConfigurationBuilder) { Enumeration urlResources = getResourceAsURL(resource); if (urlResources == null) { throw new DemoiselleConfigurationException(message.fileNotFound(resource)); } configureFileBuilder(urlResources); } else { configurations.add(builder.getConfiguration()); } } catch (IOException | ConfigurationException e) { logger.warning(message.failOnCreateApacheConfiguration(e.getMessage())); } } private void configureFileBuilder(Enumeration urlResources) { Parameters params = new Parameters(); while (urlResources.hasMoreElements()) { BasicConfigurationBuilder builder = createConfiguration(); URL url = urlResources.nextElement(); ((FileBasedConfigurationBuilder) builder).configure(params.fileBased().setURL(url)); try { configurations.add(builder.getConfiguration()); } catch (ConfigurationException e) { logger.warning(message.failOnCreateApacheConfiguration(e.getMessage())); } } } private BasicConfigurationBuilder createConfiguration() { BasicConfigurationBuilder builder; switch (configurationType) { case XML: builder = new FileBasedConfigurationBuilder<>(XMLConfiguration.class); break; case SYSTEM: builder = new BasicConfigurationBuilder<>(SystemConfiguration.class); break; default: builder = new FileBasedConfigurationBuilder<>(PropertiesConfiguration.class); } return builder; } private void identifyPrefix() { String prefixValue = targetBaseClass .getAnnotation(org.demoiselle.jee.configuration.annotation.Configuration.class).prefix(); if (prefixValue.endsWith(".")) { logger.warning(message.configurationDotAfterPrefix(resource)); } else if (!prefixValue.isEmpty()) { prefixValue += "."; } prefix = prefixValue; } private void fillTargetObjectWithValues() { fields.forEach(this::fillFieldWithValue); } /** * * Fill the field informed. * * Get the default value informed on class and retrieve value from source. * * If the value is present on field and on source, the value from source is * selected and the default value is ignored * * @param field * Current field from targetObject */ private void fillFieldWithValue(Field field) { if (hasIgnoreAnnotation(field)) { return; } Object defaultValue = getFieldValueFromObject(field, targetObject); Object loadedValue = getValueFromSource(field, getKey(field)); Object finalValue = loadedValue == null ? defaultValue : loadedValue; if (loadedValue == null) { logger.info(message.configurationKeyNotFoud(prefix + getKey(field))); } setFieldValue(field, targetObject, finalValue); } private Object getValueFromSource(Field field, String key) { Object value = null; try { ConfigurationValueExtractor extractor = getValueExtractor(field); for (Configuration config : configurations) { if (value != null) { break; } value = extractor.getValue(prefix, key, field, config); } } catch (DemoiselleConfigurationException cause) { throw cause; } catch (DemoiselleConfigurationValueExtractorException cause) { throw new DemoiselleConfigurationException( message.configurationNotConversion(prefix + getKey(field), field.getType().toString()), cause); } catch (Exception cause) { throw new DemoiselleConfigurationException(message.configurationGenericExtractionError( field.getType().toString(), getValueExtractor(field).getClass().getCanonicalName()), cause); } return value; } private ConfigurationValueExtractor getValueExtractor(Field field) { Set candidates = new HashSet<>(); getExtractors().forEach(extractorClass -> { ConfigurationValueExtractor extractor = CDI.current().select(extractorClass).get(); if (extractor.isSupported(field)) { candidates.add(extractor); } }); ConfigurationValueExtractor elected = selectValueExtractorElected(candidates); if (elected == null) { throw new DemoiselleConfigurationException(message.configurationExtractorNotFound(field.toGenericString(), ConfigurationValueExtractor.class.getName()), new ClassNotFoundException()); } return elected; } private Set> getExtractors() { Set> cache = new HashSet<>(); try { ConfigurationBootstrap bootstrap = CDI.current().select(ConfigurationBootstrap.class).get(); cache = bootstrap.getCache(); } catch (IllegalStateException e) { // CDI is not already logger.finest(message.cdiNotAlready()); } return cache; } private String getKey(Field field) { String key; if (field.isAnnotationPresent(Name.class)) { key = field.getAnnotation(Name.class).value(); } else { key = field.getName(); } return key; } private boolean hasIgnoreAnnotation(Field field) { return field.isAnnotationPresent(Ignore.class); } private void validateValues() { for (Field field : fields) { validateValue(field); } } @SuppressWarnings({ "rawtypes", "unchecked" }) private void validateValue(Field field) { ValidatorFactory dfv = Validation.buildDefaultValidatorFactory(); Validator validator = dfv.getValidator(); Set violations = validator.validateProperty(targetObject, field.getName()); StringBuilder messageConstraint = new StringBuilder(); if (!violations.isEmpty()) { for (Iterator iter = violations.iterator(); iter.hasNext();) { ConstraintViolation violation = (ConstraintViolation) iter.next(); messageConstraint.append(field.toGenericString() + " " + violation.getMessage() + "\n"); } throw new DemoiselleConfigurationException(messageConstraint.toString(), new ConstraintViolationException(violations)); } } private List getNonStaticFields(Class type) { List nonStaticfields = new ArrayList<>(); if (type != null) { Class currentType = type; while (currentType != null && !Object.class.getCanonicalName().equals(currentType.getCanonicalName())) { nonStaticfields.addAll(Arrays.asList(getNonStaticDeclaredFields(currentType))); currentType = currentType.getSuperclass(); } } return nonStaticfields; } private Field[] getNonStaticDeclaredFields(Class type) { List nonStaticfields = new ArrayList<>(); if (type != null) { for (Field field : type.getDeclaredFields()) { if (!Modifier.isStatic(field.getModifiers()) && !field.getType().equals(type.getDeclaringClass())) { nonStaticfields.add(field); } } } return nonStaticfields.toArray(new Field[0]); } private Enumeration getResourceAsURL(final String resource) throws IOException { ClassLoader classLoader = getClassLoaderForResource(resource); return classLoader != null ? classLoader.getResources(resource) : null; } private ClassLoader getClassLoaderForResource(final String resource) { final String stripped = resource.charAt(0) == '/' ? resource.substring(1) : resource; URL url = null; ClassLoader result = Thread.currentThread().getContextClassLoader(); if (result != null) { url = result.getResource(stripped); } if (url == null) { result = getClass().getClassLoader(); url = getClass().getClassLoader().getResource(stripped); } if (url == null) { result = null; } return result; } @SuppressWarnings("unchecked") private T getFieldValueFromObject(Field field, Object object) { T result = null; try { boolean acessible = field.isAccessible(); field.setAccessible(true); result = (T) field.get(object); field.setAccessible(acessible); } catch (Exception e) { throw new DemoiselleConfigurationException( message.configurationErrorGetValue(field.getName(), object.getClass().getCanonicalName()), e); } return result; } private void setFieldValue(Field field, Object object, Object value) { try { boolean acessible = field.isAccessible(); field.setAccessible(true); field.set(object, value); field.setAccessible(acessible); } catch (Exception e) { throw new DemoiselleConfigurationException( message.configurationErrorSetValue(value, field.getName(), object.getClass().getCanonicalName()), e); } } private ConfigurationValueExtractor selectValueExtractorElected(Set candidates) { Map, ConfigurationValueExtractor> map = new HashMap<>(); candidates.stream().filter(Objects::nonNull).forEach(candidate -> map.put(candidate.getClass(), candidate)); Class elected = selectClass(map.keySet()); return map.get(elected); } private Class selectClass( Set> candidates) { Class selected = null; for (Class candidate : candidates) { Boolean isCandidateInternalValueExtractor = isIntenalVauleExtractor(candidate); Boolean isSelectedInternalValueExtractor = isIntenalVauleExtractor(selected); if (selected == null || (isSelectedInternalValueExtractor == Boolean.TRUE && isCandidateInternalValueExtractor == Boolean.FALSE)) { selected = candidate; } } if (selected != null) { performAmbiguityCheck(selected, candidates); } return selected; } private Boolean isIntenalVauleExtractor(Class candidate) { if (candidate == null) { return Boolean.FALSE; } ConfigurationInternalDemoiselleValueExtractor frameworkPackageExtractor = candidate.getPackage() .getAnnotation(ConfigurationInternalDemoiselleValueExtractor.class); return frameworkPackageExtractor != null ? Boolean.TRUE : Boolean.FALSE; } private void performAmbiguityCheck(Class selected, Set> candidates) { Set> ambiguous = new HashSet<>(); for (Class candidate : candidates) { Boolean isCandidateInternalValueExtractor = isIntenalVauleExtractor(candidate); Boolean isSelectedInternalValueExtractor = isIntenalVauleExtractor(selected); if (selected != candidate && (isSelectedInternalValueExtractor == Boolean.TRUE && isCandidateInternalValueExtractor == Boolean.FALSE)) { ambiguous.add(candidate); } } if (!ambiguous.isEmpty()) { ambiguous.add(selected); String exceptionMessage = getExceptionMessage(ambiguous); throw new DemoiselleConfigurationException(exceptionMessage, new AmbiguousResolutionException()); } } private String getExceptionMessage(Set> ambiguousCandidates) { StringBuilder classes = new StringBuilder(); Class type = ambiguousCandidates.iterator().next(); int i = 0; for (Class clazz : ambiguousCandidates) { if (i++ != 0) { classes.append(", "); } classes.append(clazz.getCanonicalName()); } return message.ambigousStrategyResolution(type.getCanonicalName(), classes.toString()); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy