diff --git a/demoiselle-configuration/.gitignore b/demoiselle-configuration/.gitignore new file mode 100644 index 0000000..d18ac71 --- /dev/null +++ b/demoiselle-configuration/.gitignore @@ -0,0 +1,4 @@ +target/ +.project +.settings/ +.classpath diff --git a/demoiselle-configuration/pom.xml b/demoiselle-configuration/pom.xml new file mode 100644 index 0000000..36f8292 --- /dev/null +++ b/demoiselle-configuration/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + demoiselle-configuration + jar + + Demoiselle Configuration + + Demoiselle Configuration habilita os projetos a usarem configurações em arquivos .properties, .xml ou variáveis de ambiente. + + + + org.demoiselle.jee + demoiselle-parent + 3.0.0-BETA1-SNAPSHOT + ../demoiselle-parent + + + + + + org.demoiselle.jee + demoiselle-core + + + + + org.apache.commons + commons-configuration2 + 2.0 + + + + + + commons-beanutils + commons-beanutils + 1.9.2 + + + + diff --git a/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/ConfigType.java b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/ConfigType.java new file mode 100644 index 0000000..fad93e3 --- /dev/null +++ b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/ConfigType.java @@ -0,0 +1,25 @@ +package org.demoiselle.jee.configuration; + +/** + * Defines configuration types to be loaded. + * + * @author SERPRO + */ +public enum ConfigType { + + /** + * Configuration loaded on {@link System#getProperties()} or {@link System#getenv()}. + */ + SYSTEM, + + /** + * Configuration loaded on XML resources. + */ + XML, + + /** + * Configuration loaded on properties resources. + */ + PROPERTIES + +} diff --git a/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/ConfigurationBootstrap.java b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/ConfigurationBootstrap.java new file mode 100644 index 0000000..8118890 --- /dev/null +++ b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/ConfigurationBootstrap.java @@ -0,0 +1,38 @@ +package org.demoiselle.jee.configuration; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; + +import javax.enterprise.event.Observes; +import javax.enterprise.inject.spi.Extension; +import javax.enterprise.inject.spi.ProcessAnnotatedType; + +import org.demoiselle.jee.configuration.extractor.ConfigurationValueExtractor; + +public class ConfigurationBootstrap implements Extension { + + private Collection> cache; + + public Collection> getCache() { + if (this.cache == null) { + this.cache = Collections.synchronizedSet(new HashSet>()); + } + + return this.cache; + } + + public void processAnnotatedType(@Observes final ProcessAnnotatedType pat) { + + Class pcsClass = pat.getAnnotatedType().getJavaClass(); + + if (pcsClass.isAnnotation() || pcsClass.isInterface() + || pcsClass.isSynthetic() || pcsClass.isArray() + || pcsClass.isEnum()){ + return; + } + + this.getCache().add(pat.getAnnotatedType().getJavaClass()); + } + +} diff --git a/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/ConfigurationException.java b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/ConfigurationException.java new file mode 100644 index 0000000..112e4bb --- /dev/null +++ b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/ConfigurationException.java @@ -0,0 +1,35 @@ +package org.demoiselle.jee.configuration; + +import org.demoiselle.jee.core.exception.DemoiselleException; + +/** + * Exception class intended to be used by configuration components. + * + * @author SERPRO + */ +public class ConfigurationException extends DemoiselleException{ + + private static final long serialVersionUID = 1L; + + /** + * Constructor with message. + * + * @param message + * exception message + */ + public ConfigurationException(String message) { + super(message); + } + + /** + * Constructor with message and cause. + * + * @param message + * exception message + * @param cause + * exception cause + */ + public ConfigurationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/ConfigurationInterceptor.java b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/ConfigurationInterceptor.java new file mode 100644 index 0000000..c8046f9 --- /dev/null +++ b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/ConfigurationInterceptor.java @@ -0,0 +1,32 @@ +package org.demoiselle.jee.configuration; + +import javax.annotation.Priority; +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.spi.CDI; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; + +import org.demoiselle.jee.configuration.annotation.Configuration; + +/** + *

+ * Interceptor class that loads the values of configuration files + * into it's mapped class. + *

+ */ +@Dependent +@Configuration +@Interceptor +@Priority(Interceptor.Priority.APPLICATION) +public class ConfigurationInterceptor { + + @AroundInvoke + public Object constructConfiguration(final InvocationContext ic) throws Exception { + final ConfigurationLoader configurationLoader = CDI.current().select(ConfigurationLoader.class).get(); + + final Class baseClass = ic.getMethod().getDeclaringClass(); + configurationLoader.load(ic.getTarget(), baseClass); + return ic.proceed(); + } +} diff --git a/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/ConfigurationLoader.java b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/ConfigurationLoader.java new file mode 100644 index 0000000..f560c03 --- /dev/null +++ b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/ConfigurationLoader.java @@ -0,0 +1,512 @@ +package org.demoiselle.jee.configuration; + +import static org.demoiselle.jee.configuration.ConfigType.SYSTEM; + +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.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +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.FileBasedConfiguration; +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.ConversionException; +import org.demoiselle.jee.configuration.extractor.ConfigurationValueExtractor; +import org.demoiselle.jee.configuration.message.ConfigurationMessage; +import org.demoiselle.jee.core.annotation.Ignore; +import org.demoiselle.jee.core.annotation.Name; +import org.demoiselle.jee.core.annotation.Priority; + +/** + * This component loads a config class annotated with {@link org.demoiselle.jee.configuration.annotation.configuration.Configuration} + * by filling its attributes with {@link org.demoiselle.jsf.util.Parameter} + * according to a {@link org.demoiselle.configuration.ConfigType}. + * + * @author SERPRO + */ +@ApplicationScoped +public class ConfigurationLoader implements Serializable { + + private static final long serialVersionUID = 1L; + + @Inject + private ConfigurationMessage bundle; + + @Inject + private Logger logger; + + private Object object; + + private Class baseClass; + + private ConfigType type; + + private String resource; + + private String prefix; + + private Configuration configuration; + + private Collection fields; + + private final Map loadedCache = new ConcurrentHashMap<>(); + + public void load(final Object object, Class baseClass) throws ConfigurationException { + Boolean isLoaded = loadedCache.get(object); + + if (isLoaded == null || !isLoaded) { + try { + loadConfiguration(object, baseClass, true); + loadedCache.put(object, true); + } catch (ConfigurationException c) { + c.printStackTrace(); + loadedCache.put(object, false); + throw c; + } + } + } + + public void load(final Object object, Class baseClass, boolean logLoadingProcess) throws ConfigurationException { + Boolean isLoaded = loadedCache.get(object); + + if (isLoaded == null || !isLoaded) { + try { + loadConfiguration(object, baseClass, logLoadingProcess); + loadedCache.put(object, true); + } catch (ConfigurationException c) { + loadedCache.put(object, false); + throw c; + } + } + } + + private void loadConfiguration(final Object object, Class baseClass, boolean logLoadingProcess) + throws ConfigurationException { + + if (logLoadingProcess) { + logger.fine(bundle.loadConfigurationClass(baseClass.getName())); + } + + this.object = object; + this.baseClass = baseClass; + + loadFields(); + validateFields(); + + loadType(); + loadResource(); + loadConfiguration(); + + if (this.configuration != null) { + loadPrefix(); + loadValues(); + } + + validateValues(); + } + + private void loadFields() { + this.fields = getNonStaticFields(baseClass); + } + + private void validateFields() { + this.fields.forEach(this::validateField); + } + + private void validateField(Field field) { + Name annotation = field.getAnnotation(Name.class); + + if (annotation != null && annotation.value().isEmpty()) { + throw new ConfigurationException(bundle.configurationNameAttributeCantBeEmpty(), new IllegalArgumentException()); + } + } + + private void loadType() { + this.type = baseClass.getAnnotation(org.demoiselle.jee.configuration.annotation.Configuration.class).type(); + } + + private void loadResource() { + if (this.type != SYSTEM) { + String name = baseClass.getAnnotation(org.demoiselle.jee.configuration.annotation.Configuration.class).resource(); + String extension = this.type.toString().toLowerCase(); + + this.resource = name + "." + extension; + } + } + + private void loadConfiguration() { + Configuration config; + BasicConfigurationBuilder builder = createConfiguration(); + + if (builder instanceof FileBasedConfigurationBuilder) { + Parameters params = new Parameters(); + + ((FileBasedConfigurationBuilder) builder).configure(params.fileBased().setURL(getResourceAsURL(this.resource))); + } + + try { + config = builder.getConfiguration(); + } + catch (org.apache.commons.configuration2.ex.ConfigurationException e) { + logger.warning(bundle.fileNotFound(this.resource)); + config = null; + } + + this.configuration = config; + } + + private BasicConfigurationBuilder createConfiguration() { + BasicConfigurationBuilder builder; + + switch (this.type) { + 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 loadPrefix() { + String prefix = baseClass.getAnnotation(org.demoiselle.jee.configuration.annotation.Configuration.class).prefix(); + + if (prefix.endsWith(".")) { + logger.warning(bundle.configurationDotAfterPrefix(this.resource)); + } else if (!prefix.isEmpty()) { + prefix += "."; + } + + this.prefix = prefix; + } + + private void loadValues() { + this.fields.forEach(this::loadValue); + } + + private void loadValue(Field field) { + if (hasIgnore(field)) { + return; + } + + Object defaultValue = getFieldValue(field, this.object); + Object loadedValue = getValue(field, field.getType(), getKey(field), defaultValue); + Object finalValue = (loadedValue == null ? defaultValue : loadedValue); + + if (loadedValue == null) { + logger.fine(bundle.configurationKeyNotFoud(this.prefix + getKey(field))); + } + + setFieldValue(field, this.object, finalValue); + logger.finer(bundle.configurationFieldLoaded(this.prefix + getKey(field), finalValue == null ? "null" : finalValue)); + } + + private Object getValue(Field field, Class type, String key, Object defaultValue) { + Object value = null; + + try { + ConfigurationValueExtractor extractor = getValueExtractor(field); + value = extractor.getValue(this.prefix, key, field, this.configuration); + } + catch (ConfigurationException cause) { + cause.printStackTrace(); + throw cause; + } + catch (ConversionException cause) { + throw new ConfigurationException(bundle.configurationNotConversion(this.prefix + getKey(field), field.getType().toString()), cause); + } + catch (Exception cause) { + throw new ConfigurationException(bundle.configurationGenericExtractionError(field.getType().toString(), getValueExtractor(field).getClass().getCanonicalName()), cause); + } + + return value; + } + + private ConfigurationValueExtractor getValueExtractor(Field field) { + Collection candidates = new HashSet(); + ConfigurationBootstrap bootstrap = CDI.current().select(ConfigurationBootstrap.class).get(); + + for (Class extractorClass : bootstrap.getCache()) { + ConfigurationValueExtractor extractor = CDI.current().select(extractorClass).get(); + + if (extractor.isSupported(field)) { + candidates.add(extractor); + } + } + + ConfigurationValueExtractor elected = selectReference(ConfigurationValueExtractor.class, candidates); + + if (elected == null) { + throw new ConfigurationException(bundle.configurationExtractorNotFound(field.toGenericString(), + ConfigurationValueExtractor.class.getName()), new ClassNotFoundException()); + } + + return elected; + } + + 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 hasIgnore(Field field) { + return field.isAnnotationPresent(Ignore.class); + } + + private void validateValues() { + for (Field field : this.fields) { + validateValue(field, getFieldValue(field, this.object)); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private void validateValue(Field field, Object value) { + ValidatorFactory dfv = Validation.buildDefaultValidatorFactory(); + Validator validator = dfv.getValidator(); + + Set violations = validator.validateProperty(this.object, field.getName()); + + StringBuilder message = new StringBuilder(); + + if (!violations.isEmpty()) { + for (Iterator iter = violations.iterator(); iter.hasNext(); ) { + ConstraintViolation violation = (ConstraintViolation) iter.next(); + message.append(field.toGenericString() + " " + violation.getMessage() + "\n"); + } + + throw new ConfigurationException(message.toString(), new ConstraintViolationException(violations)); + } + } + + + + + /** + * @param type Base type to look for fields + * @return All non static fields from a certain type, including fields declared in superclasses of this type. + */ + public List getNonStaticFields(Class type) { + List fields = new ArrayList(); + + if (type != null) { + Class currentType = type; + while (currentType != null && !"java.lang.Object".equals(currentType.getCanonicalName())) { + fields.addAll(Arrays.asList(getNonStaticDeclaredFields(currentType))); + currentType = currentType.getSuperclass(); + } + } + + return fields; + } + + /** + * @param type Base type to look for fields + * @return All non static fields from a certain type. Inherited fields are not returned, so if you need to get + * inherited fields you must iterate over this type's hierarchy. + */ + public Field[] getNonStaticDeclaredFields(Class type) { + List fields = new ArrayList(); + + if (type != null) { + for (Field field : type.getDeclaredFields()) { + if (!Modifier.isStatic(field.getModifiers()) && !field.getType().equals(type.getDeclaringClass())) { + fields.add(field); + } + } + } + + return fields.toArray(new Field[0]); + } + + /** + * Return an URL to access a resource available to the active classloader for the calling thread. + * + * @param resource String representation of the location of the resource on the classpath + * @return The {@link URL} for the resource + */ + public URL getResourceAsURL(final String resource) { + ClassLoader classLoader = getClassLoaderForResource(resource); + return classLoader != null ? classLoader.getResource(resource) : null; + } + + /** + * Obtains the {@link ClassLoader} for the given resource. + * + * @param resource String representation of the fully qualified path to the resource on the classpath + * @return {@link ClassLoader} ClassLoader for the given resource. + */ + public 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") + public T getFieldValue(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 ConfigurationException(bundle.configurationErrorGetValue(field.getName(), object.getClass().getCanonicalName()), e); + } + + return result; + } + + /** + * Sets a value in a field. + * + * @param field field to be setted. + * @param object object that contains the field. + * @param value value to be setted in the field. + */ + public 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 ConfigurationException(bundle.configurationErrorSetValue(value, field.getName(), object.getClass().getCanonicalName()), e); + } + } + + @SuppressWarnings("unchecked") + private T selectReference(Class type, Collection options) { + + Map, T> map = new HashMap<>(); + + options.stream() + .filter(instance -> instance != null) + .forEach(instance -> { + map.put((Class) instance.getClass(), instance); + }); + + Class elected = selectClass(type, map.keySet()); + return map.get(elected); + } + + private Class selectClass(Class type, Collection> options) { + Class selected = null; + + for (Class option : options) { + if (selected == null || getPriority(option) < getPriority(selected)) { + selected = option; + } + } + + if (selected != null) { + performAmbiguityCheck(type, selected, options); + } + + return selected; + } + + private void performAmbiguityCheck(Class type, Class selected, + Collection> options) { + int selectedPriority = getPriority(selected); + + List> ambiguous = new ArrayList>(); + + for (Class option : options) { + if (selected != option && selectedPriority == getPriority(option)) { + ambiguous.add(option); + } + } + + if (!ambiguous.isEmpty()) { + ambiguous.add(selected); + + String message = getExceptionMessage(type, ambiguous); + throw new ConfigurationException(message, new AmbiguousResolutionException()); + } + } + + private int getPriority(Class type) { + int result = Priority.MAX_PRIORITY; + Priority priority = type.getAnnotation(Priority.class); + + if (priority != null) { + result = priority.value(); + } + + return result; + } + + private String getExceptionMessage(Class type, List> ambiguous) { + StringBuffer classes = new StringBuffer(); + + int i = 0; + for (Class clazz : ambiguous) { + if (i++ != 0) { + classes.append(", "); + } + + classes.append(clazz.getCanonicalName()); + } + + return bundle.ambigousStrategyResolution(type.getCanonicalName(), classes.toString()); + } +} + diff --git a/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/annotation/Configuration.java b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/annotation/Configuration.java new file mode 100644 index 0000000..3c519bf --- /dev/null +++ b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/annotation/Configuration.java @@ -0,0 +1,82 @@ +package org.demoiselle.jee.configuration.annotation; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Stereotype; +import javax.enterprise.util.Nonbinding; +import javax.inject.Named; +import javax.interceptor.InterceptorBinding; + +import org.demoiselle.jee.configuration.ConfigType; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + *

+ * Identifies a configuration class, that is, a structure reserved to store configuration values retrieved from a + * given resource file or system variables. + *

+ *

+ * Configuration resources are application scoped, meaning only one instance can ever exist in + * a running application. For that reason usually configuration fields are immutable, to avoid + * changes made in one context affecting other contexts in a running application. + *

+ *

A Configuration is:

+ *
    + *
  • defined when annotated with {@code @Configuration}
  • + *
  • automatically injected whenever {@code @Inject} is used
  • + *
+ * + * @author SERPRO + */ +@ApplicationScoped +@Named +@InterceptorBinding +@Stereotype +@Target(TYPE) +@Retention(RUNTIME) +public @interface Configuration { + + /** + * Define the default prefix. + */ + String DEFAULT_PREFIX = "demoiselle"; + + /** + * Define the default resource. + */ + String DEFAULT_RESOURCE = "demoiselle"; + + /** + * Defines the resource type to be used: a properties file, an XML file, or system variables. + * If not specified, a properties resource file is to be considered. + * + * @return ConfigType Type of configuration resource file to look for + */ + @Nonbinding ConfigType type() default ConfigType.PROPERTIES; + + /** + * Defines an optional prefix to be used on every parameter key. + * For instance, if prefix is set to "demoiselle.pagination" and an attribute named + * defaultPageSize is found in the class, the corresponding key + * demoiselle.pagination.defaultPageSize is expected to be read in the resource file. + * + * @return String prefix common to all attributes to be read by the configuration class + */ + @Nonbinding String prefix() default DEFAULT_PREFIX; + + /** + * Defines the resource file name to be read by this configuration class. There is no need to specify file extension + * in the case of properties or XML resources. + * For instance, when resource is set to "bookmark" and the type set to properties, a corresponding + * file named bookmark.properties is considered. + * If not specified, the default configuration file demoiselle.properties is used instead. + * + * @return String Name of the resource file to look for (minus file extension) + */ + @Nonbinding String resource() default DEFAULT_RESOURCE; + +} diff --git a/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/ConfigurationValueExtractor.java b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/ConfigurationValueExtractor.java new file mode 100644 index 0000000..6f974af --- /dev/null +++ b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/ConfigurationValueExtractor.java @@ -0,0 +1,51 @@ +package org.demoiselle.jee.configuration.extractor; + +import java.lang.reflect.Field; + +import org.apache.commons.configuration2.Configuration; + +/** + *

+ * Interface that defines how to convert values extracted from configuration + * files to fields in a class annotated with {@link Configuration}. + *

+ * + *

+ * Primitive types like int and float, their wrapper + * counterparts like {@link Integer} and {@link Float} and the {@link String} class + * can already be converted by the framework, this interface is reserved for specialized + * classes. + *

+ * + * @author SERPRO + */ +public interface ConfigurationValueExtractor { + + /** + * Method that must appropriately extract the value from a property file and set this value to a + * field in a configuration class. + * + * @param prefix + * optional parte of property name that must be concatenated with key to form the whole + * property name. + * @param key + * key of the property. + * @param field + * configuration field to be setted. + * @param configuration + * a configuration object. + * @return current value of this property + * @throws Exception if the value can't be extracted from the property file + */ + Object getValue(String prefix, String key, Field field, Configuration configuration) throws Exception; + + /** + * Checks if the extractor class is appropriate to extract values to the type of deffined by parameter + * field. + * + * @param field + * field to be checked. + * @return true if this extractor can convert this field into the extractor's final type + */ + boolean isSupported(Field field); +} diff --git a/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/impl/ConfigurationArrayValueExtractor.java b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/impl/ConfigurationArrayValueExtractor.java new file mode 100644 index 0000000..42ef76c --- /dev/null +++ b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/impl/ConfigurationArrayValueExtractor.java @@ -0,0 +1,24 @@ +package org.demoiselle.jee.configuration.extractor.impl; + +import java.lang.reflect.Field; + +import javax.enterprise.context.Dependent; + +import org.apache.commons.configuration2.Configuration; +import org.apache.commons.configuration2.DataConfiguration; +import org.demoiselle.jee.configuration.extractor.ConfigurationValueExtractor; +import org.demoiselle.jee.core.annotation.Priority; +import static org.demoiselle.jee.core.annotation.Priority.*; + +@Dependent +@Priority(L2_PRIORITY) +public class ConfigurationArrayValueExtractor implements ConfigurationValueExtractor { + + public Object getValue(String prefix, String key, Field field, Configuration configuration) throws Exception { + return new DataConfiguration(configuration).getArray(field.getType().getComponentType(), prefix + key); + } + + public boolean isSupported(Field field) { + return field.getType().isArray(); + } +} diff --git a/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/impl/ConfigurationClassValueExtractor.java b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/impl/ConfigurationClassValueExtractor.java new file mode 100644 index 0000000..85411e2 --- /dev/null +++ b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/impl/ConfigurationClassValueExtractor.java @@ -0,0 +1,65 @@ +package org.demoiselle.jee.configuration.extractor.impl; + +import java.lang.reflect.Field; + +import java.net.URL; + +import javax.enterprise.context.Dependent; + +import org.apache.commons.configuration2.Configuration; +import org.demoiselle.jee.configuration.extractor.ConfigurationValueExtractor; +import org.demoiselle.jee.core.annotation.Priority; + +import static org.demoiselle.jee.core.annotation.Priority.*; + +@Dependent +@Priority(L2_PRIORITY) +public class ConfigurationClassValueExtractor implements ConfigurationValueExtractor { + + public Object getValue(String prefix, String key, Field field, Configuration configuration) throws Exception { + Object value = null; + String canonicalName = configuration.getString(prefix + key); + + if (canonicalName != null) { + value = forName(canonicalName); + } + + return value; + } + + public boolean isSupported(Field field) { + return field.getType() == Class.class; + } + + @SuppressWarnings("unchecked") + public Class forName(final String className) throws ClassNotFoundException { + ClassLoader classLoader = getClassLoaderForClass(className); + return (Class) Class.forName(className, true, classLoader); + } + + public ClassLoader getClassLoaderForClass(final String canonicalName) { + return getClassLoaderForResource(canonicalName.replaceAll("\\.", "/") + ".class"); + } + + public 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; + } +} diff --git a/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/impl/ConfigurationEnumValueExtractor.java b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/impl/ConfigurationEnumValueExtractor.java new file mode 100644 index 0000000..93b5d09 --- /dev/null +++ b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/impl/ConfigurationEnumValueExtractor.java @@ -0,0 +1,42 @@ +package org.demoiselle.jee.configuration.extractor.impl; + +import java.lang.reflect.Field; + +import javax.enterprise.context.Dependent; + +import org.apache.commons.configuration2.Configuration; +import org.apache.commons.configuration2.ex.ConversionException;// +import org.demoiselle.jee.configuration.extractor.ConfigurationValueExtractor; +import org.demoiselle.jee.configuration.message.ConfigurationMessage; +import org.demoiselle.jee.core.annotation.Priority; +import static org.demoiselle.jee.core.annotation.Priority.*; + +@Dependent +@Priority(L2_PRIORITY) +public class ConfigurationEnumValueExtractor implements ConfigurationValueExtractor { + + private transient ConfigurationMessage bundle; + + public Object getValue(String prefix, String key, Field field, Configuration configuration) throws Exception { + String value = configuration.getString(prefix + key); + + if (value != null && !value.trim().equals("")) { + Object enums[] = field.getType().getEnumConstants(); + + for (int i = 0; i < enums.length; i++) { + if (((Enum) enums[i]).name().equals(value)) { + return enums[i]; + } + } + } else { + return null; + } + + throw new ConversionException(bundle.configurationNotConversion(value, field.getDeclaringClass().getCanonicalName())); + } + + public boolean isSupported(Field field) { + return field.getType().isEnum(); + } + +} diff --git a/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/impl/ConfigurationMapValueExtractor.java b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/impl/ConfigurationMapValueExtractor.java new file mode 100644 index 0000000..d1d4707 --- /dev/null +++ b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/impl/ConfigurationMapValueExtractor.java @@ -0,0 +1,52 @@ +package org.demoiselle.jee.configuration.extractor.impl; + +import java.lang.reflect.Field; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.enterprise.context.Dependent; + +import org.apache.commons.configuration2.Configuration; +import org.demoiselle.jee.configuration.extractor.ConfigurationValueExtractor; +import org.demoiselle.jee.core.annotation.Priority; + +import static org.demoiselle.jee.core.annotation.Priority.*; + + +@Dependent +@Priority(L2_PRIORITY) +public class ConfigurationMapValueExtractor implements ConfigurationValueExtractor { + + public Object getValue(String prefix, String key, Field field, Configuration configuration) throws Exception { + Map value = null; + + String regexp = "^(" + prefix + ")(" + key + ")(\\.(\\w+))?$"; + Pattern pattern = Pattern.compile(regexp); + + for (Iterator iter = configuration.getKeys(); iter.hasNext();) { + String iterKey = iter.next(); + Matcher matcher = pattern.matcher(iterKey); + + if (matcher.matches()) { + String confKey = matcher.group(1) + matcher.group(2) + ( matcher.group(3)!=null ? matcher.group(3) : "" ); + + if (value == null) { + value = new HashMap<>(); + } + + String mapKey = matcher.group(4) == null ? "default" : matcher.group(4); + value.put(mapKey, configuration.getString(confKey)); + } + } + + return value; + } + + public boolean isSupported(Field field) { + return field.getType() == Map.class; + } +} diff --git a/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/impl/ConfigurationPrimitiveOrWrapperValueExtractor.java b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/impl/ConfigurationPrimitiveOrWrapperValueExtractor.java new file mode 100644 index 0000000..c8d0271 --- /dev/null +++ b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/impl/ConfigurationPrimitiveOrWrapperValueExtractor.java @@ -0,0 +1,55 @@ +package org.demoiselle.jee.configuration.extractor.impl; + +import java.lang.reflect.Field; + +import java.util.HashSet; +import java.util.Set; + +import javax.enterprise.context.Dependent; + +import org.apache.commons.configuration2.Configuration; +import org.apache.commons.configuration2.DataConfiguration; +import org.apache.commons.configuration2.ex.ConversionException; +import org.apache.commons.lang3.ClassUtils; +import org.demoiselle.jee.configuration.extractor.ConfigurationValueExtractor; + +import org.demoiselle.jee.core.annotation.Priority; + +import static org.demoiselle.jee.core.annotation.Priority.*; + +@Dependent +@Priority(L2_PRIORITY) +public class ConfigurationPrimitiveOrWrapperValueExtractor implements ConfigurationValueExtractor { + + private static final Set wrappers = new HashSet(); + + static { + wrappers.add(Boolean.class); + wrappers.add(Byte.class); + wrappers.add(Character.class); + wrappers.add(Short.class); + wrappers.add(Integer.class); + wrappers.add(Long.class); + wrappers.add(Double.class); + wrappers.add(Float.class); + wrappers.add(Void.TYPE); + } + + public Object getValue(String prefix, String key, Field field, Configuration configuration) throws Exception { + Object value; + + try { + value = new DataConfiguration(configuration) + .get(ClassUtils.primitiveToWrapper(field.getType()), prefix + key); + + } catch (ConversionException cause) { + throw cause; + } + + return value; + } + + public boolean isSupported(Field field) { + return field.getType().isPrimitive() || wrappers.contains(field.getType()); + } +} diff --git a/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/impl/ConfigurationStringValueExtractor.java b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/impl/ConfigurationStringValueExtractor.java new file mode 100644 index 0000000..89aa1e5 --- /dev/null +++ b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/impl/ConfigurationStringValueExtractor.java @@ -0,0 +1,24 @@ +package org.demoiselle.jee.configuration.extractor.impl; + +import java.lang.reflect.Field; + +import javax.enterprise.context.Dependent; + +import org.apache.commons.configuration2.Configuration; +import org.demoiselle.jee.configuration.extractor.ConfigurationValueExtractor; +import org.demoiselle.jee.core.annotation.Priority; + +import static org.demoiselle.jee.core.annotation.Priority.*; + +@Dependent +@Priority(L2_PRIORITY) +public class ConfigurationStringValueExtractor implements ConfigurationValueExtractor { + + public Object getValue(String prefix, String key, Field field, Configuration configuration) throws Exception { + return configuration.getString(prefix + key); + } + + public boolean isSupported(Field field) { + return field.getType() == String.class; + } +} diff --git a/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/message/ConfigurationMessage.java b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/message/ConfigurationMessage.java new file mode 100644 index 0000000..acd8731 --- /dev/null +++ b/demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/message/ConfigurationMessage.java @@ -0,0 +1,46 @@ +package org.demoiselle.jee.configuration.message; + +import org.apache.deltaspike.core.api.message.MessageBundle; +import org.apache.deltaspike.core.api.message.MessageTemplate; + +@MessageBundle +public interface ConfigurationMessage { + + @MessageTemplate("{load-configuration-class}") + String loadConfigurationClass(String name); + + @MessageTemplate("{configuration-name-attribute-cant-be-empty}") + String configurationNameAttributeCantBeEmpty(); + + @MessageTemplate("{file-not-found}") + String fileNotFound(String resource); + + @MessageTemplate("{configuration-dot-after-prefix}") + String configurationDotAfterPrefix(String resource); + + @MessageTemplate("{configuration-key-not-found}") + String configurationKeyNotFoud(String string); + + @MessageTemplate("{configuration-field-loaded}") + String configurationFieldLoaded(String string, Object object); + + @MessageTemplate("{configuration-not-conversion}") + String configurationNotConversion(String string, String string2); + + @MessageTemplate("{configuration-generic-extraction-error}") + String configurationGenericExtractionError(String string, String canonicalName); + + @MessageTemplate("{configuration-extractor-not-found}") + String configurationExtractorNotFound(String genericString, String name); + + @MessageTemplate("{ambiguous-strategy-resolution}") + String ambigousStrategyResolution(String canonicalName, String string); + + @MessageTemplate("{configuration-error-get-value}") + String configurationErrorGetValue(String string, Object object); + + @MessageTemplate("{configuration-error-set-value}") + String configurationErrorSetValue(Object value, Object field, Object object); + + +} diff --git a/demoiselle-configuration/src/main/resources/META-INF/beans.xml b/demoiselle-configuration/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000..e818cd2 --- /dev/null +++ b/demoiselle-configuration/src/main/resources/META-INF/beans.xml @@ -0,0 +1,6 @@ + + + + diff --git a/demoiselle-configuration/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/demoiselle-configuration/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension new file mode 100644 index 0000000..0dc2434 --- /dev/null +++ b/demoiselle-configuration/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension @@ -0,0 +1 @@ +org.demoiselle.jee.configuration.ConfigurationBootstrap \ No newline at end of file diff --git a/demoiselle-configuration/src/main/resources/org/demoiselle/jee/configuration/message/ConfigurationMessage.properties b/demoiselle-configuration/src/main/resources/org/demoiselle/jee/configuration/message/ConfigurationMessage.properties new file mode 100644 index 0000000..d809e11 --- /dev/null +++ b/demoiselle-configuration/src/main/resources/org/demoiselle/jee/configuration/message/ConfigurationMessage.properties @@ -0,0 +1,12 @@ +load-configuration-class=Carregando a classe de configura\u00E7\u00E3o %s +configuration-name-attribute-cant-be-empty=A nota\u00E7\u00E3o @Name n\u00E3o pode estar em branco +file-not-found=O arquivo %s n\u00E3o foi encontrado +configuration-dot-after-prefix=N\u00E3o \u00E9 necess\u00E1rio adicionar o ponto ap\u00F3s o prefixo para uma classe de configura\u00E7\u00E3o. \u00C9 recomendado que sejam retirados, pois poder\u00E3o causar erros em vers\u00F5es futuras do Framework. +configuration-key-not-found=%s\: [n\u00E3o encontrada] +configuration-field-loaded=%s: %s +configuration-not-conversion=N\u00E3o \u00E9 poss\u00EDvel converter o valor %s para o tipo %s +configuration-generic-extraction-error=Ocorreu um erro durante a extra\u00E7\u00E3o do tipo %s com o extrator %s +configuration-extractor-not-found=N\u00E3o foi poss\u00EDvel encontrar a classe extratora para o atributo %s. Implemente a interface %s para criar sua classe extratora. +ambiguous-strategy-resolution=Foi detectada ambiguidade da interface %s com as seguintes implementa\u00E7\u00F5es\: %s. Para resolver o conflito, defina explicitamente a implementa\u00E7\u00E3o no demoiselle.properties. +configuration-error-get-value=N\u00E3o \u00E9 poss\u00EDvel obter o valor do campo %s da classe %s +configuration-error-set-value=N\u00E3o \u00E9 poss\u00EDvel setar o valor %s no campo %s da classe %s \ No newline at end of file diff --git a/pom.xml b/pom.xml index dcbd9fa..1401f5e 100644 --- a/pom.xml +++ b/pom.xml @@ -72,6 +72,7 @@ demoiselle-security-token demoiselle-security-basic demoiselle-security-jwt + demoiselle-configuration -- libgit2 0.21.2