Commit ea988efd086a7b4523ae47cd84f66462ef4426df

Authored by Clovis Lemes Ferreira Junior
1 parent 986dccd7

Demoiselle Configuration - first commit

Showing 20 changed files with 1150 additions and 0 deletions   Show diff stats
demoiselle-configuration/.gitignore 0 → 100644
... ... @@ -0,0 +1,4 @@
  1 +target/
  2 +.project
  3 +.settings/
  4 +.classpath
... ...
demoiselle-configuration/pom.xml 0 → 100644
... ... @@ -0,0 +1,43 @@
  1 +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3 +
  4 + <modelVersion>4.0.0</modelVersion>
  5 + <artifactId>demoiselle-configuration</artifactId>
  6 + <packaging>jar</packaging>
  7 +
  8 + <name>Demoiselle Configuration</name>
  9 + <description>
  10 + Demoiselle Configuration habilita os projetos a usarem configurações em arquivos .properties, .xml ou variáveis de ambiente.
  11 + </description>
  12 +
  13 + <parent>
  14 + <groupId>org.demoiselle.jee</groupId>
  15 + <artifactId>demoiselle-parent</artifactId>
  16 + <version>3.0.0-BETA1-SNAPSHOT</version>
  17 + <relativePath>../demoiselle-parent</relativePath>
  18 + </parent>
  19 +
  20 + <dependencies>
  21 +
  22 + <dependency>
  23 + <groupId>org.demoiselle.jee</groupId>
  24 + <artifactId>demoiselle-core</artifactId>
  25 + </dependency>
  26 +
  27 + <!-- commons-configuration2 -->
  28 + <dependency>
  29 + <groupId>org.apache.commons</groupId>
  30 + <artifactId>commons-configuration2</artifactId>
  31 + <version>2.0</version>
  32 + </dependency>
  33 +
  34 +
  35 + <!-- commons-beanutils -->
  36 + <dependency>
  37 + <groupId>commons-beanutils</groupId>
  38 + <artifactId>commons-beanutils</artifactId>
  39 + <version>1.9.2</version>
  40 + </dependency>
  41 +
  42 + </dependencies>
  43 +</project>
... ...
demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/ConfigType.java 0 → 100644
... ... @@ -0,0 +1,25 @@
  1 +package org.demoiselle.jee.configuration;
  2 +
  3 +/**
  4 + * Defines configuration types to be loaded.
  5 + *
  6 + * @author SERPRO
  7 + */
  8 +public enum ConfigType {
  9 +
  10 + /**
  11 + * Configuration loaded on {@link System#getProperties()} or {@link System#getenv()}.
  12 + */
  13 + SYSTEM,
  14 +
  15 + /**
  16 + * Configuration loaded on XML resources.
  17 + */
  18 + XML,
  19 +
  20 + /**
  21 + * Configuration loaded on properties resources.
  22 + */
  23 + PROPERTIES
  24 +
  25 +}
... ...
demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/ConfigurationBootstrap.java 0 → 100644
... ... @@ -0,0 +1,38 @@
  1 +package org.demoiselle.jee.configuration;
  2 +
  3 +import java.util.Collection;
  4 +import java.util.Collections;
  5 +import java.util.HashSet;
  6 +
  7 +import javax.enterprise.event.Observes;
  8 +import javax.enterprise.inject.spi.Extension;
  9 +import javax.enterprise.inject.spi.ProcessAnnotatedType;
  10 +
  11 +import org.demoiselle.jee.configuration.extractor.ConfigurationValueExtractor;
  12 +
  13 +public class ConfigurationBootstrap implements Extension {
  14 +
  15 + private Collection<Class<? extends ConfigurationValueExtractor>> cache;
  16 +
  17 + public Collection<Class<? extends ConfigurationValueExtractor>> getCache() {
  18 + if (this.cache == null) {
  19 + this.cache = Collections.synchronizedSet(new HashSet<Class<? extends ConfigurationValueExtractor>>());
  20 + }
  21 +
  22 + return this.cache;
  23 + }
  24 +
  25 + public void processAnnotatedType(@Observes final ProcessAnnotatedType<? extends ConfigurationValueExtractor> pat) {
  26 +
  27 + Class<? extends ConfigurationValueExtractor> pcsClass = pat.getAnnotatedType().getJavaClass();
  28 +
  29 + if (pcsClass.isAnnotation() || pcsClass.isInterface()
  30 + || pcsClass.isSynthetic() || pcsClass.isArray()
  31 + || pcsClass.isEnum()){
  32 + return;
  33 + }
  34 +
  35 + this.getCache().add(pat.getAnnotatedType().getJavaClass());
  36 + }
  37 +
  38 +}
... ...
demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/ConfigurationException.java 0 → 100644
... ... @@ -0,0 +1,35 @@
  1 +package org.demoiselle.jee.configuration;
  2 +
  3 +import org.demoiselle.jee.core.exception.DemoiselleException;
  4 +
  5 +/**
  6 + * Exception class intended to be used by configuration components.
  7 + *
  8 + * @author SERPRO
  9 + */
  10 +public class ConfigurationException extends DemoiselleException{
  11 +
  12 + private static final long serialVersionUID = 1L;
  13 +
  14 + /**
  15 + * Constructor with message.
  16 + *
  17 + * @param message
  18 + * exception message
  19 + */
  20 + public ConfigurationException(String message) {
  21 + super(message);
  22 + }
  23 +
  24 + /**
  25 + * Constructor with message and cause.
  26 + *
  27 + * @param message
  28 + * exception message
  29 + * @param cause
  30 + * exception cause
  31 + */
  32 + public ConfigurationException(String message, Throwable cause) {
  33 + super(message, cause);
  34 + }
  35 +}
... ...
demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/ConfigurationInterceptor.java 0 → 100644
... ... @@ -0,0 +1,32 @@
  1 +package org.demoiselle.jee.configuration;
  2 +
  3 +import javax.annotation.Priority;
  4 +import javax.enterprise.context.Dependent;
  5 +import javax.enterprise.inject.spi.CDI;
  6 +import javax.interceptor.AroundInvoke;
  7 +import javax.interceptor.Interceptor;
  8 +import javax.interceptor.InvocationContext;
  9 +
  10 +import org.demoiselle.jee.configuration.annotation.Configuration;
  11 +
  12 +/**
  13 + * <p>
  14 + * Interceptor class that loads the values of configuration files
  15 + * into it's mapped class.
  16 + * </p>
  17 + */
  18 +@Dependent
  19 +@Configuration
  20 +@Interceptor
  21 +@Priority(Interceptor.Priority.APPLICATION)
  22 +public class ConfigurationInterceptor {
  23 +
  24 + @AroundInvoke
  25 + public Object constructConfiguration(final InvocationContext ic) throws Exception {
  26 + final ConfigurationLoader configurationLoader = CDI.current().select(ConfigurationLoader.class).get();
  27 +
  28 + final Class<?> baseClass = ic.getMethod().getDeclaringClass();
  29 + configurationLoader.load(ic.getTarget(), baseClass);
  30 + return ic.proceed();
  31 + }
  32 +}
... ...
demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/ConfigurationLoader.java 0 → 100644
... ... @@ -0,0 +1,512 @@
  1 +package org.demoiselle.jee.configuration;
  2 +
  3 +import static org.demoiselle.jee.configuration.ConfigType.SYSTEM;
  4 +
  5 +import java.io.Serializable;
  6 +import java.lang.reflect.Field;
  7 +import java.lang.reflect.Modifier;
  8 +import java.net.URL;
  9 +import java.util.ArrayList;
  10 +import java.util.Arrays;
  11 +import java.util.Collection;
  12 +import java.util.HashMap;
  13 +import java.util.HashSet;
  14 +import java.util.Iterator;
  15 +import java.util.List;
  16 +import java.util.Map;
  17 +import java.util.Set;
  18 +import java.util.concurrent.ConcurrentHashMap;
  19 +import java.util.logging.Logger;
  20 +
  21 +import javax.enterprise.context.ApplicationScoped;
  22 +import javax.enterprise.inject.AmbiguousResolutionException;
  23 +import javax.enterprise.inject.spi.CDI;
  24 +import javax.inject.Inject;
  25 +import javax.validation.ConstraintViolation;
  26 +import javax.validation.ConstraintViolationException;
  27 +import javax.validation.Validation;
  28 +import javax.validation.Validator;
  29 +import javax.validation.ValidatorFactory;
  30 +
  31 +import org.apache.commons.configuration2.Configuration;
  32 +import org.apache.commons.configuration2.FileBasedConfiguration;
  33 +import org.apache.commons.configuration2.PropertiesConfiguration;
  34 +import org.apache.commons.configuration2.SystemConfiguration;
  35 +import org.apache.commons.configuration2.XMLConfiguration;
  36 +import org.apache.commons.configuration2.builder.BasicConfigurationBuilder;
  37 +import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
  38 +import org.apache.commons.configuration2.builder.fluent.Parameters;
  39 +import org.apache.commons.configuration2.ex.ConversionException;
  40 +import org.demoiselle.jee.configuration.extractor.ConfigurationValueExtractor;
  41 +import org.demoiselle.jee.configuration.message.ConfigurationMessage;
  42 +import org.demoiselle.jee.core.annotation.Ignore;
  43 +import org.demoiselle.jee.core.annotation.Name;
  44 +import org.demoiselle.jee.core.annotation.Priority;
  45 +
  46 +/**
  47 + * This component loads a config class annotated with {@link org.demoiselle.jee.configuration.annotation.configuration.Configuration}
  48 + * by filling its attributes with {@link org.demoiselle.jsf.util.Parameter}
  49 + * according to a {@link org.demoiselle.configuration.ConfigType}.
  50 + *
  51 + * @author SERPRO
  52 + */
  53 +@ApplicationScoped
  54 +public class ConfigurationLoader implements Serializable {
  55 +
  56 + private static final long serialVersionUID = 1L;
  57 +
  58 + @Inject
  59 + private ConfigurationMessage bundle;
  60 +
  61 + @Inject
  62 + private Logger logger;
  63 +
  64 + private Object object;
  65 +
  66 + private Class<?> baseClass;
  67 +
  68 + private ConfigType type;
  69 +
  70 + private String resource;
  71 +
  72 + private String prefix;
  73 +
  74 + private Configuration configuration;
  75 +
  76 + private Collection<Field> fields;
  77 +
  78 + private final Map<Object, Boolean> loadedCache = new ConcurrentHashMap<>();
  79 +
  80 + public void load(final Object object, Class<?> baseClass) throws ConfigurationException {
  81 + Boolean isLoaded = loadedCache.get(object);
  82 +
  83 + if (isLoaded == null || !isLoaded) {
  84 + try {
  85 + loadConfiguration(object, baseClass, true);
  86 + loadedCache.put(object, true);
  87 + } catch (ConfigurationException c) {
  88 + c.printStackTrace();
  89 + loadedCache.put(object, false);
  90 + throw c;
  91 + }
  92 + }
  93 + }
  94 +
  95 + public void load(final Object object, Class<?> baseClass, boolean logLoadingProcess) throws ConfigurationException {
  96 + Boolean isLoaded = loadedCache.get(object);
  97 +
  98 + if (isLoaded == null || !isLoaded) {
  99 + try {
  100 + loadConfiguration(object, baseClass, logLoadingProcess);
  101 + loadedCache.put(object, true);
  102 + } catch (ConfigurationException c) {
  103 + loadedCache.put(object, false);
  104 + throw c;
  105 + }
  106 + }
  107 + }
  108 +
  109 + private void loadConfiguration(final Object object, Class<?> baseClass, boolean logLoadingProcess)
  110 + throws ConfigurationException {
  111 +
  112 + if (logLoadingProcess) {
  113 + logger.fine(bundle.loadConfigurationClass(baseClass.getName()));
  114 + }
  115 +
  116 + this.object = object;
  117 + this.baseClass = baseClass;
  118 +
  119 + loadFields();
  120 + validateFields();
  121 +
  122 + loadType();
  123 + loadResource();
  124 + loadConfiguration();
  125 +
  126 + if (this.configuration != null) {
  127 + loadPrefix();
  128 + loadValues();
  129 + }
  130 +
  131 + validateValues();
  132 + }
  133 +
  134 + private void loadFields() {
  135 + this.fields = getNonStaticFields(baseClass);
  136 + }
  137 +
  138 + private void validateFields() {
  139 + this.fields.forEach(this::validateField);
  140 + }
  141 +
  142 + private void validateField(Field field) {
  143 + Name annotation = field.getAnnotation(Name.class);
  144 +
  145 + if (annotation != null && annotation.value().isEmpty()) {
  146 + throw new ConfigurationException(bundle.configurationNameAttributeCantBeEmpty(), new IllegalArgumentException());
  147 + }
  148 + }
  149 +
  150 + private void loadType() {
  151 + this.type = baseClass.getAnnotation(org.demoiselle.jee.configuration.annotation.Configuration.class).type();
  152 + }
  153 +
  154 + private void loadResource() {
  155 + if (this.type != SYSTEM) {
  156 + String name = baseClass.getAnnotation(org.demoiselle.jee.configuration.annotation.Configuration.class).resource();
  157 + String extension = this.type.toString().toLowerCase();
  158 +
  159 + this.resource = name + "." + extension;
  160 + }
  161 + }
  162 +
  163 + private void loadConfiguration() {
  164 + Configuration config;
  165 + BasicConfigurationBuilder<? extends Configuration> builder = createConfiguration();
  166 +
  167 + if (builder instanceof FileBasedConfigurationBuilder) {
  168 + Parameters params = new Parameters();
  169 +
  170 + ((FileBasedConfigurationBuilder<?>) builder).configure(params.fileBased().setURL(getResourceAsURL(this.resource)));
  171 + }
  172 +
  173 + try {
  174 + config = builder.getConfiguration();
  175 + }
  176 + catch (org.apache.commons.configuration2.ex.ConfigurationException e) {
  177 + logger.warning(bundle.fileNotFound(this.resource));
  178 + config = null;
  179 + }
  180 +
  181 + this.configuration = config;
  182 + }
  183 +
  184 + private BasicConfigurationBuilder<? extends Configuration> createConfiguration() {
  185 + BasicConfigurationBuilder<? extends Configuration> builder;
  186 +
  187 + switch (this.type) {
  188 + case XML:
  189 + builder = new FileBasedConfigurationBuilder<XMLConfiguration>(XMLConfiguration.class);
  190 + break;
  191 +
  192 + case SYSTEM:
  193 + builder = new BasicConfigurationBuilder<>(SystemConfiguration.class);
  194 + break;
  195 +
  196 + default:
  197 + builder = new FileBasedConfigurationBuilder<FileBasedConfiguration>(PropertiesConfiguration.class);
  198 + }
  199 +
  200 + return builder;
  201 + }
  202 +
  203 + private void loadPrefix() {
  204 + String prefix = baseClass.getAnnotation(org.demoiselle.jee.configuration.annotation.Configuration.class).prefix();
  205 +
  206 + if (prefix.endsWith(".")) {
  207 + logger.warning(bundle.configurationDotAfterPrefix(this.resource));
  208 + } else if (!prefix.isEmpty()) {
  209 + prefix += ".";
  210 + }
  211 +
  212 + this.prefix = prefix;
  213 + }
  214 +
  215 + private void loadValues() {
  216 + this.fields.forEach(this::loadValue);
  217 + }
  218 +
  219 + private void loadValue(Field field) {
  220 + if (hasIgnore(field)) {
  221 + return;
  222 + }
  223 +
  224 + Object defaultValue = getFieldValue(field, this.object);
  225 + Object loadedValue = getValue(field, field.getType(), getKey(field), defaultValue);
  226 + Object finalValue = (loadedValue == null ? defaultValue : loadedValue);
  227 +
  228 + if (loadedValue == null) {
  229 + logger.fine(bundle.configurationKeyNotFoud(this.prefix + getKey(field)));
  230 + }
  231 +
  232 + setFieldValue(field, this.object, finalValue);
  233 + logger.finer(bundle.configurationFieldLoaded(this.prefix + getKey(field), finalValue == null ? "null" : finalValue));
  234 + }
  235 +
  236 + private Object getValue(Field field, Class<?> type, String key, Object defaultValue) {
  237 + Object value = null;
  238 +
  239 + try {
  240 + ConfigurationValueExtractor extractor = getValueExtractor(field);
  241 + value = extractor.getValue(this.prefix, key, field, this.configuration);
  242 + }
  243 + catch (ConfigurationException cause) {
  244 + cause.printStackTrace();
  245 + throw cause;
  246 + }
  247 + catch (ConversionException cause) {
  248 + throw new ConfigurationException(bundle.configurationNotConversion(this.prefix + getKey(field), field.getType().toString()), cause);
  249 + }
  250 + catch (Exception cause) {
  251 + throw new ConfigurationException(bundle.configurationGenericExtractionError(field.getType().toString(), getValueExtractor(field).getClass().getCanonicalName()), cause);
  252 + }
  253 +
  254 + return value;
  255 + }
  256 +
  257 + private ConfigurationValueExtractor getValueExtractor(Field field) {
  258 + Collection<ConfigurationValueExtractor> candidates = new HashSet<ConfigurationValueExtractor>();
  259 + ConfigurationBootstrap bootstrap = CDI.current().select(ConfigurationBootstrap.class).get();
  260 +
  261 + for (Class<? extends ConfigurationValueExtractor> extractorClass : bootstrap.getCache()) {
  262 + ConfigurationValueExtractor extractor = CDI.current().select(extractorClass).get();
  263 +
  264 + if (extractor.isSupported(field)) {
  265 + candidates.add(extractor);
  266 + }
  267 + }
  268 +
  269 + ConfigurationValueExtractor elected = selectReference(ConfigurationValueExtractor.class, candidates);
  270 +
  271 + if (elected == null) {
  272 + throw new ConfigurationException(bundle.configurationExtractorNotFound(field.toGenericString(),
  273 + ConfigurationValueExtractor.class.getName()), new ClassNotFoundException());
  274 + }
  275 +
  276 + return elected;
  277 + }
  278 +
  279 + private String getKey(Field field) {
  280 + String key;
  281 +
  282 + if (field.isAnnotationPresent(Name.class)) {
  283 + key = field.getAnnotation(Name.class).value();
  284 + } else {
  285 + key = field.getName();
  286 + }
  287 +
  288 + return key;
  289 + }
  290 +
  291 + private boolean hasIgnore(Field field) {
  292 + return field.isAnnotationPresent(Ignore.class);
  293 + }
  294 +
  295 + private void validateValues() {
  296 + for (Field field : this.fields) {
  297 + validateValue(field, getFieldValue(field, this.object));
  298 + }
  299 + }
  300 +
  301 + @SuppressWarnings({ "rawtypes", "unchecked" })
  302 + private void validateValue(Field field, Object value) {
  303 + ValidatorFactory dfv = Validation.buildDefaultValidatorFactory();
  304 + Validator validator = dfv.getValidator();
  305 +
  306 + Set violations = validator.validateProperty(this.object, field.getName());
  307 +
  308 + StringBuilder message = new StringBuilder();
  309 +
  310 + if (!violations.isEmpty()) {
  311 + for (Iterator iter = violations.iterator(); iter.hasNext(); ) {
  312 + ConstraintViolation violation = (ConstraintViolation) iter.next();
  313 + message.append(field.toGenericString() + " " + violation.getMessage() + "\n");
  314 + }
  315 +
  316 + throw new ConfigurationException(message.toString(), new ConstraintViolationException(violations));
  317 + }
  318 + }
  319 +
  320 +
  321 +
  322 +
  323 + /**
  324 + * @param type Base type to look for fields
  325 + * @return All non static fields from a certain type, including fields declared in superclasses of this type.
  326 + */
  327 + public List<Field> getNonStaticFields(Class<?> type) {
  328 + List<Field> fields = new ArrayList<Field>();
  329 +
  330 + if (type != null) {
  331 + Class<?> currentType = type;
  332 + while (currentType != null && !"java.lang.Object".equals(currentType.getCanonicalName())) {
  333 + fields.addAll(Arrays.asList(getNonStaticDeclaredFields(currentType)));
  334 + currentType = currentType.getSuperclass();
  335 + }
  336 + }
  337 +
  338 + return fields;
  339 + }
  340 +
  341 + /**
  342 + * @param type Base type to look for fields
  343 + * @return All non static fields from a certain type. Inherited fields are not returned, so if you need to get
  344 + * inherited fields you must iterate over this type's hierarchy.
  345 + */
  346 + public Field[] getNonStaticDeclaredFields(Class<?> type) {
  347 + List<Field> fields = new ArrayList<Field>();
  348 +
  349 + if (type != null) {
  350 + for (Field field : type.getDeclaredFields()) {
  351 + if (!Modifier.isStatic(field.getModifiers()) && !field.getType().equals(type.getDeclaringClass())) {
  352 + fields.add(field);
  353 + }
  354 + }
  355 + }
  356 +
  357 + return fields.toArray(new Field[0]);
  358 + }
  359 +
  360 + /**
  361 + * Return an URL to access a resource available to the active classloader for the calling thread.
  362 + *
  363 + * @param resource String representation of the location of the resource on the classpath
  364 + * @return The {@link URL} for the resource
  365 + */
  366 + public URL getResourceAsURL(final String resource) {
  367 + ClassLoader classLoader = getClassLoaderForResource(resource);
  368 + return classLoader != null ? classLoader.getResource(resource) : null;
  369 + }
  370 +
  371 + /**
  372 + * Obtains the {@link ClassLoader} for the given resource.
  373 + *
  374 + * @param resource String representation of the fully qualified path to the resource on the classpath
  375 + * @return {@link ClassLoader} ClassLoader for the given resource.
  376 + */
  377 + public ClassLoader getClassLoaderForResource(final String resource) {
  378 + final String stripped = resource.charAt(0) == '/' ? resource.substring(1) : resource;
  379 +
  380 + URL url = null;
  381 + ClassLoader result = Thread.currentThread().getContextClassLoader();
  382 +
  383 + if (result != null) {
  384 + url = result.getResource(stripped);
  385 + }
  386 +
  387 + if (url == null) {
  388 + result = getClass().getClassLoader();
  389 + url = getClass().getClassLoader().getResource(stripped);
  390 + }
  391 +
  392 + if (url == null) {
  393 + result = null;
  394 + }
  395 +
  396 + return result;
  397 + }
  398 +
  399 + @SuppressWarnings("unchecked")
  400 + public <T> T getFieldValue(Field field, Object object) {
  401 + T result = null;
  402 +
  403 + try {
  404 + boolean acessible = field.isAccessible();
  405 + field.setAccessible(true);
  406 + result = (T) field.get(object);
  407 + field.setAccessible(acessible);
  408 +
  409 + } catch (Exception e) {
  410 + throw new ConfigurationException(bundle.configurationErrorGetValue(field.getName(), object.getClass().getCanonicalName()), e);
  411 + }
  412 +
  413 + return result;
  414 + }
  415 +
  416 + /**
  417 + * Sets a value in a field.
  418 + *
  419 + * @param field field to be setted.
  420 + * @param object object that contains the field.
  421 + * @param value value to be setted in the field.
  422 + */
  423 + public void setFieldValue(Field field, Object object, Object value) {
  424 + try {
  425 + boolean acessible = field.isAccessible();
  426 + field.setAccessible(true);
  427 + field.set(object, value);
  428 + field.setAccessible(acessible);
  429 +
  430 + } catch (Exception e) {
  431 + throw new ConfigurationException(bundle.configurationErrorSetValue(value, field.getName(), object.getClass().getCanonicalName()), e);
  432 + }
  433 + }
  434 +
  435 + @SuppressWarnings("unchecked")
  436 + private <T> T selectReference(Class<T> type, Collection<? extends T> options) {
  437 +
  438 + Map<Class<? extends T>, T> map = new HashMap<>();
  439 +
  440 + options.stream()
  441 + .filter(instance -> instance != null)
  442 + .forEach(instance -> {
  443 + map.put((Class<T>) instance.getClass(), instance);
  444 + });
  445 +
  446 + Class<? extends T> elected = selectClass(type, map.keySet());
  447 + return map.get(elected);
  448 + }
  449 +
  450 + private <T> Class<? extends T> selectClass(Class<T> type, Collection<Class<? extends T>> options) {
  451 + Class<? extends T> selected = null;
  452 +
  453 + for (Class<? extends T> option : options) {
  454 + if (selected == null || getPriority(option) < getPriority(selected)) {
  455 + selected = option;
  456 + }
  457 + }
  458 +
  459 + if (selected != null) {
  460 + performAmbiguityCheck(type, selected, options);
  461 + }
  462 +
  463 + return selected;
  464 + }
  465 +
  466 + private <T> void performAmbiguityCheck(Class<T> type, Class<? extends T> selected,
  467 + Collection<Class<? extends T>> options) {
  468 + int selectedPriority = getPriority(selected);
  469 +
  470 + List<Class<? extends T>> ambiguous = new ArrayList<Class<? extends T>>();
  471 +
  472 + for (Class<? extends T> option : options) {
  473 + if (selected != option && selectedPriority == getPriority(option)) {
  474 + ambiguous.add(option);
  475 + }
  476 + }
  477 +
  478 + if (!ambiguous.isEmpty()) {
  479 + ambiguous.add(selected);
  480 +
  481 + String message = getExceptionMessage(type, ambiguous);
  482 + throw new ConfigurationException(message, new AmbiguousResolutionException());
  483 + }
  484 + }
  485 +
  486 + private <T> int getPriority(Class<T> type) {
  487 + int result = Priority.MAX_PRIORITY;
  488 + Priority priority = type.getAnnotation(Priority.class);
  489 +
  490 + if (priority != null) {
  491 + result = priority.value();
  492 + }
  493 +
  494 + return result;
  495 + }
  496 +
  497 + private <T> String getExceptionMessage(Class<T> type, List<Class<? extends T>> ambiguous) {
  498 + StringBuffer classes = new StringBuffer();
  499 +
  500 + int i = 0;
  501 + for (Class<? extends T> clazz : ambiguous) {
  502 + if (i++ != 0) {
  503 + classes.append(", ");
  504 + }
  505 +
  506 + classes.append(clazz.getCanonicalName());
  507 + }
  508 +
  509 + return bundle.ambigousStrategyResolution(type.getCanonicalName(), classes.toString());
  510 + }
  511 +}
  512 +
... ...
demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/annotation/Configuration.java 0 → 100644
... ... @@ -0,0 +1,82 @@
  1 +package org.demoiselle.jee.configuration.annotation;
  2 +
  3 +import javax.enterprise.context.ApplicationScoped;
  4 +import javax.enterprise.inject.Stereotype;
  5 +import javax.enterprise.util.Nonbinding;
  6 +import javax.inject.Named;
  7 +import javax.interceptor.InterceptorBinding;
  8 +
  9 +import org.demoiselle.jee.configuration.ConfigType;
  10 +
  11 +import java.lang.annotation.Retention;
  12 +import java.lang.annotation.Target;
  13 +
  14 +import static java.lang.annotation.ElementType.TYPE;
  15 +import static java.lang.annotation.RetentionPolicy.RUNTIME;
  16 +
  17 +/**
  18 + * <p>
  19 + * Identifies a <b>configuration class</b>, that is, a structure reserved to store configuration values retrieved from a
  20 + * given resource file or system variables.
  21 + * </p>
  22 + * <p>
  23 + * Configuration resources are application scoped, meaning only one instance can ever exist in
  24 + * a running application. For that reason usually configuration fields are immutable, to avoid
  25 + * changes made in one context affecting other contexts in a running application.
  26 + * </p>
  27 + * <p>A <i>Configuration</i> is:</p>
  28 + * <ul>
  29 + * <li>defined when annotated with {@code @Configuration}</li>
  30 + * <li>automatically injected whenever {@code @Inject} is used</li>
  31 + * </ul>
  32 + *
  33 + * @author SERPRO
  34 + */
  35 +@ApplicationScoped
  36 +@Named
  37 +@InterceptorBinding
  38 +@Stereotype
  39 +@Target(TYPE)
  40 +@Retention(RUNTIME)
  41 +public @interface Configuration {
  42 +
  43 + /**
  44 + * Define the default prefix.
  45 + */
  46 + String DEFAULT_PREFIX = "demoiselle";
  47 +
  48 + /**
  49 + * Define the default resource.
  50 + */
  51 + String DEFAULT_RESOURCE = "demoiselle";
  52 +
  53 + /**
  54 + * Defines the resource type to be used: a properties file, an XML file, or system variables.
  55 + * If not specified, a properties resource file is to be considered.
  56 + *
  57 + * @return ConfigType Type of configuration resource file to look for
  58 + */
  59 + @Nonbinding ConfigType type() default ConfigType.PROPERTIES;
  60 +
  61 + /**
  62 + * Defines an optional prefix to be used on every parameter key.
  63 + * For instance, if prefix is set to <code>"demoiselle.pagination"</code> and an attribute named
  64 + * <code>defaultPageSize</code> is found in the class, the corresponding key
  65 + * <code>demoiselle.pagination.defaultPageSize</code> is expected to be read in the resource file.
  66 + *
  67 + * @return String prefix common to all attributes to be read by the configuration class
  68 + */
  69 + @Nonbinding String prefix() default DEFAULT_PREFIX;
  70 +
  71 + /**
  72 + * Defines the resource file name to be read by this configuration class. There is no need to specify file extension
  73 + * in the case of properties or XML resources.
  74 + * For instance, when resource is set to <code>"bookmark"</code> and the type set to properties, a corresponding
  75 + * file named <code>bookmark.properties</code> is considered.
  76 + * If not specified, the default configuration file <code>demoiselle.properties</code> is used instead.
  77 + *
  78 + * @return String Name of the resource file to look for (minus file extension)
  79 + */
  80 + @Nonbinding String resource() default DEFAULT_RESOURCE;
  81 +
  82 +}
... ...
demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/ConfigurationValueExtractor.java 0 → 100644
... ... @@ -0,0 +1,51 @@
  1 +package org.demoiselle.jee.configuration.extractor;
  2 +
  3 +import java.lang.reflect.Field;
  4 +
  5 +import org.apache.commons.configuration2.Configuration;
  6 +
  7 +/**
  8 + * <p>
  9 + * Interface that defines how to convert values extracted from configuration
  10 + * files to fields in a class annotated with {@link Configuration}.
  11 + * </p>
  12 + *
  13 + * <p>
  14 + * Primitive types like <code>int</code> and <code>float</code>, their wrapper
  15 + * counterparts like {@link Integer} and {@link Float} and the {@link String} class
  16 + * can already be converted by the framework, this interface is reserved for specialized
  17 + * classes.
  18 + * </p>
  19 + *
  20 + * @author SERPRO
  21 + */
  22 +public interface ConfigurationValueExtractor {
  23 +
  24 + /**
  25 + * Method that must appropriately extract the value from a property file and set this value to a
  26 + * field in a configuration class.
  27 + *
  28 + * @param prefix
  29 + * optional parte of property name that must be concatenated with <b>key</b> to form the whole
  30 + * property name.
  31 + * @param key
  32 + * key of the property.
  33 + * @param field
  34 + * configuration field to be setted.
  35 + * @param configuration
  36 + * a configuration object.
  37 + * @return current value of this property
  38 + * @throws Exception if the value can't be extracted from the property file
  39 + */
  40 + Object getValue(String prefix, String key, Field field, Configuration configuration) throws Exception;
  41 +
  42 + /**
  43 + * Checks if the extractor class is appropriate to extract values to the type of deffined by parameter
  44 + * <b>field</b>.
  45 + *
  46 + * @param field
  47 + * field to be checked.
  48 + * @return <code>true</code> if this extractor can convert this field into the extractor's final type
  49 + */
  50 + boolean isSupported(Field field);
  51 +}
... ...
demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/impl/ConfigurationArrayValueExtractor.java 0 → 100644
... ... @@ -0,0 +1,24 @@
  1 +package org.demoiselle.jee.configuration.extractor.impl;
  2 +
  3 +import java.lang.reflect.Field;
  4 +
  5 +import javax.enterprise.context.Dependent;
  6 +
  7 +import org.apache.commons.configuration2.Configuration;
  8 +import org.apache.commons.configuration2.DataConfiguration;
  9 +import org.demoiselle.jee.configuration.extractor.ConfigurationValueExtractor;
  10 +import org.demoiselle.jee.core.annotation.Priority;
  11 +import static org.demoiselle.jee.core.annotation.Priority.*;
  12 +
  13 +@Dependent
  14 +@Priority(L2_PRIORITY)
  15 +public class ConfigurationArrayValueExtractor implements ConfigurationValueExtractor {
  16 +
  17 + public Object getValue(String prefix, String key, Field field, Configuration configuration) throws Exception {
  18 + return new DataConfiguration(configuration).getArray(field.getType().getComponentType(), prefix + key);
  19 + }
  20 +
  21 + public boolean isSupported(Field field) {
  22 + return field.getType().isArray();
  23 + }
  24 +}
... ...
demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/impl/ConfigurationClassValueExtractor.java 0 → 100644
... ... @@ -0,0 +1,65 @@
  1 +package org.demoiselle.jee.configuration.extractor.impl;
  2 +
  3 +import java.lang.reflect.Field;
  4 +
  5 +import java.net.URL;
  6 +
  7 +import javax.enterprise.context.Dependent;
  8 +
  9 +import org.apache.commons.configuration2.Configuration;
  10 +import org.demoiselle.jee.configuration.extractor.ConfigurationValueExtractor;
  11 +import org.demoiselle.jee.core.annotation.Priority;
  12 +
  13 +import static org.demoiselle.jee.core.annotation.Priority.*;
  14 +
  15 +@Dependent
  16 +@Priority(L2_PRIORITY)
  17 +public class ConfigurationClassValueExtractor implements ConfigurationValueExtractor {
  18 +
  19 + public Object getValue(String prefix, String key, Field field, Configuration configuration) throws Exception {
  20 + Object value = null;
  21 + String canonicalName = configuration.getString(prefix + key);
  22 +
  23 + if (canonicalName != null) {
  24 + value = forName(canonicalName);
  25 + }
  26 +
  27 + return value;
  28 + }
  29 +
  30 + public boolean isSupported(Field field) {
  31 + return field.getType() == Class.class;
  32 + }
  33 +
  34 + @SuppressWarnings("unchecked")
  35 + public <T> Class<T> forName(final String className) throws ClassNotFoundException {
  36 + ClassLoader classLoader = getClassLoaderForClass(className);
  37 + return (Class<T>) Class.forName(className, true, classLoader);
  38 + }
  39 +
  40 + public ClassLoader getClassLoaderForClass(final String canonicalName) {
  41 + return getClassLoaderForResource(canonicalName.replaceAll("\\.", "/") + ".class");
  42 + }
  43 +
  44 + public ClassLoader getClassLoaderForResource(final String resource) {
  45 + final String stripped = resource.charAt(0) == '/' ? resource.substring(1) : resource;
  46 +
  47 + URL url = null;
  48 + ClassLoader result = Thread.currentThread().getContextClassLoader();
  49 +
  50 + if (result != null) {
  51 + url = result.getResource(stripped);
  52 + }
  53 +
  54 + if (url == null) {
  55 + result = getClass().getClassLoader();
  56 + url = getClass().getClassLoader().getResource(stripped);
  57 + }
  58 +
  59 + if (url == null) {
  60 + result = null;
  61 + }
  62 +
  63 + return result;
  64 + }
  65 +}
... ...
demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/impl/ConfigurationEnumValueExtractor.java 0 → 100644
... ... @@ -0,0 +1,42 @@
  1 +package org.demoiselle.jee.configuration.extractor.impl;
  2 +
  3 +import java.lang.reflect.Field;
  4 +
  5 +import javax.enterprise.context.Dependent;
  6 +
  7 +import org.apache.commons.configuration2.Configuration;
  8 +import org.apache.commons.configuration2.ex.ConversionException;//
  9 +import org.demoiselle.jee.configuration.extractor.ConfigurationValueExtractor;
  10 +import org.demoiselle.jee.configuration.message.ConfigurationMessage;
  11 +import org.demoiselle.jee.core.annotation.Priority;
  12 +import static org.demoiselle.jee.core.annotation.Priority.*;
  13 +
  14 +@Dependent
  15 +@Priority(L2_PRIORITY)
  16 +public class ConfigurationEnumValueExtractor implements ConfigurationValueExtractor {
  17 +
  18 + private transient ConfigurationMessage bundle;
  19 +
  20 + public Object getValue(String prefix, String key, Field field, Configuration configuration) throws Exception {
  21 + String value = configuration.getString(prefix + key);
  22 +
  23 + if (value != null && !value.trim().equals("")) {
  24 + Object enums[] = field.getType().getEnumConstants();
  25 +
  26 + for (int i = 0; i < enums.length; i++) {
  27 + if (((Enum<?>) enums[i]).name().equals(value)) {
  28 + return enums[i];
  29 + }
  30 + }
  31 + } else {
  32 + return null;
  33 + }
  34 +
  35 + throw new ConversionException(bundle.configurationNotConversion(value, field.getDeclaringClass().getCanonicalName()));
  36 + }
  37 +
  38 + public boolean isSupported(Field field) {
  39 + return field.getType().isEnum();
  40 + }
  41 +
  42 +}
... ...
demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/impl/ConfigurationMapValueExtractor.java 0 → 100644
... ... @@ -0,0 +1,52 @@
  1 +package org.demoiselle.jee.configuration.extractor.impl;
  2 +
  3 +import java.lang.reflect.Field;
  4 +
  5 +import java.util.HashMap;
  6 +import java.util.Iterator;
  7 +import java.util.Map;
  8 +import java.util.regex.Matcher;
  9 +import java.util.regex.Pattern;
  10 +
  11 +import javax.enterprise.context.Dependent;
  12 +
  13 +import org.apache.commons.configuration2.Configuration;
  14 +import org.demoiselle.jee.configuration.extractor.ConfigurationValueExtractor;
  15 +import org.demoiselle.jee.core.annotation.Priority;
  16 +
  17 +import static org.demoiselle.jee.core.annotation.Priority.*;
  18 +
  19 +
  20 +@Dependent
  21 +@Priority(L2_PRIORITY)
  22 +public class ConfigurationMapValueExtractor implements ConfigurationValueExtractor {
  23 +
  24 + public Object getValue(String prefix, String key, Field field, Configuration configuration) throws Exception {
  25 + Map<String, Object> value = null;
  26 +
  27 + String regexp = "^(" + prefix + ")(" + key + ")(\\.(\\w+))?$";
  28 + Pattern pattern = Pattern.compile(regexp);
  29 +
  30 + for (Iterator<String> iter = configuration.getKeys(); iter.hasNext();) {
  31 + String iterKey = iter.next();
  32 + Matcher matcher = pattern.matcher(iterKey);
  33 +
  34 + if (matcher.matches()) {
  35 + String confKey = matcher.group(1) + matcher.group(2) + ( matcher.group(3)!=null ? matcher.group(3) : "" );
  36 +
  37 + if (value == null) {
  38 + value = new HashMap<>();
  39 + }
  40 +
  41 + String mapKey = matcher.group(4) == null ? "default" : matcher.group(4);
  42 + value.put(mapKey, configuration.getString(confKey));
  43 + }
  44 + }
  45 +
  46 + return value;
  47 + }
  48 +
  49 + public boolean isSupported(Field field) {
  50 + return field.getType() == Map.class;
  51 + }
  52 +}
... ...
demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/impl/ConfigurationPrimitiveOrWrapperValueExtractor.java 0 → 100644
... ... @@ -0,0 +1,55 @@
  1 +package org.demoiselle.jee.configuration.extractor.impl;
  2 +
  3 +import java.lang.reflect.Field;
  4 +
  5 +import java.util.HashSet;
  6 +import java.util.Set;
  7 +
  8 +import javax.enterprise.context.Dependent;
  9 +
  10 +import org.apache.commons.configuration2.Configuration;
  11 +import org.apache.commons.configuration2.DataConfiguration;
  12 +import org.apache.commons.configuration2.ex.ConversionException;
  13 +import org.apache.commons.lang3.ClassUtils;
  14 +import org.demoiselle.jee.configuration.extractor.ConfigurationValueExtractor;
  15 +
  16 +import org.demoiselle.jee.core.annotation.Priority;
  17 +
  18 +import static org.demoiselle.jee.core.annotation.Priority.*;
  19 +
  20 +@Dependent
  21 +@Priority(L2_PRIORITY)
  22 +public class ConfigurationPrimitiveOrWrapperValueExtractor implements ConfigurationValueExtractor {
  23 +
  24 + private static final Set<Object> wrappers = new HashSet<Object>();
  25 +
  26 + static {
  27 + wrappers.add(Boolean.class);
  28 + wrappers.add(Byte.class);
  29 + wrappers.add(Character.class);
  30 + wrappers.add(Short.class);
  31 + wrappers.add(Integer.class);
  32 + wrappers.add(Long.class);
  33 + wrappers.add(Double.class);
  34 + wrappers.add(Float.class);
  35 + wrappers.add(Void.TYPE);
  36 + }
  37 +
  38 + public Object getValue(String prefix, String key, Field field, Configuration configuration) throws Exception {
  39 + Object value;
  40 +
  41 + try {
  42 + value = new DataConfiguration(configuration)
  43 + .get(ClassUtils.primitiveToWrapper(field.getType()), prefix + key);
  44 +
  45 + } catch (ConversionException cause) {
  46 + throw cause;
  47 + }
  48 +
  49 + return value;
  50 + }
  51 +
  52 + public boolean isSupported(Field field) {
  53 + return field.getType().isPrimitive() || wrappers.contains(field.getType());
  54 + }
  55 +}
... ...
demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/extractor/impl/ConfigurationStringValueExtractor.java 0 → 100644
... ... @@ -0,0 +1,24 @@
  1 +package org.demoiselle.jee.configuration.extractor.impl;
  2 +
  3 +import java.lang.reflect.Field;
  4 +
  5 +import javax.enterprise.context.Dependent;
  6 +
  7 +import org.apache.commons.configuration2.Configuration;
  8 +import org.demoiselle.jee.configuration.extractor.ConfigurationValueExtractor;
  9 +import org.demoiselle.jee.core.annotation.Priority;
  10 +
  11 +import static org.demoiselle.jee.core.annotation.Priority.*;
  12 +
  13 +@Dependent
  14 +@Priority(L2_PRIORITY)
  15 +public class ConfigurationStringValueExtractor implements ConfigurationValueExtractor {
  16 +
  17 + public Object getValue(String prefix, String key, Field field, Configuration configuration) throws Exception {
  18 + return configuration.getString(prefix + key);
  19 + }
  20 +
  21 + public boolean isSupported(Field field) {
  22 + return field.getType() == String.class;
  23 + }
  24 +}
... ...
demoiselle-configuration/src/main/java/org/demoiselle/jee/configuration/message/ConfigurationMessage.java 0 → 100644
... ... @@ -0,0 +1,46 @@
  1 +package org.demoiselle.jee.configuration.message;
  2 +
  3 +import org.apache.deltaspike.core.api.message.MessageBundle;
  4 +import org.apache.deltaspike.core.api.message.MessageTemplate;
  5 +
  6 +@MessageBundle
  7 +public interface ConfigurationMessage {
  8 +
  9 + @MessageTemplate("{load-configuration-class}")
  10 + String loadConfigurationClass(String name);
  11 +
  12 + @MessageTemplate("{configuration-name-attribute-cant-be-empty}")
  13 + String configurationNameAttributeCantBeEmpty();
  14 +
  15 + @MessageTemplate("{file-not-found}")
  16 + String fileNotFound(String resource);
  17 +
  18 + @MessageTemplate("{configuration-dot-after-prefix}")
  19 + String configurationDotAfterPrefix(String resource);
  20 +
  21 + @MessageTemplate("{configuration-key-not-found}")
  22 + String configurationKeyNotFoud(String string);
  23 +
  24 + @MessageTemplate("{configuration-field-loaded}")
  25 + String configurationFieldLoaded(String string, Object object);
  26 +
  27 + @MessageTemplate("{configuration-not-conversion}")
  28 + String configurationNotConversion(String string, String string2);
  29 +
  30 + @MessageTemplate("{configuration-generic-extraction-error}")
  31 + String configurationGenericExtractionError(String string, String canonicalName);
  32 +
  33 + @MessageTemplate("{configuration-extractor-not-found}")
  34 + String configurationExtractorNotFound(String genericString, String name);
  35 +
  36 + @MessageTemplate("{ambiguous-strategy-resolution}")
  37 + String ambigousStrategyResolution(String canonicalName, String string);
  38 +
  39 + @MessageTemplate("{configuration-error-get-value}")
  40 + String configurationErrorGetValue(String string, Object object);
  41 +
  42 + @MessageTemplate("{configuration-error-set-value}")
  43 + String configurationErrorSetValue(Object value, Object field, Object object);
  44 +
  45 +
  46 +}
... ...
demoiselle-configuration/src/main/resources/META-INF/beans.xml 0 → 100644
... ... @@ -0,0 +1,6 @@
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<beans xmlns="http://java.sun.com/xml/ns/javaee"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_1.xsd">
  5 +
  6 +</beans>
... ...
demoiselle-configuration/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension 0 → 100644
... ... @@ -0,0 +1 @@
  1 +org.demoiselle.jee.configuration.ConfigurationBootstrap
0 2 \ No newline at end of file
... ...
demoiselle-configuration/src/main/resources/org/demoiselle/jee/configuration/message/ConfigurationMessage.properties 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +load-configuration-class=Carregando a classe de configura\u00E7\u00E3o %s
  2 +configuration-name-attribute-cant-be-empty=A nota\u00E7\u00E3o @Name n\u00E3o pode estar em branco
  3 +file-not-found=O arquivo %s n\u00E3o foi encontrado
  4 +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.
  5 +configuration-key-not-found=%s\: [n\u00E3o encontrada]
  6 +configuration-field-loaded=%s: %s
  7 +configuration-not-conversion=N\u00E3o \u00E9 poss\u00EDvel converter o valor %s para o tipo %s
  8 +configuration-generic-extraction-error=Ocorreu um erro durante a extra\u00E7\u00E3o do tipo %s com o extrator %s
  9 +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.
  10 +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.
  11 +configuration-error-get-value=N\u00E3o \u00E9 poss\u00EDvel obter o valor do campo %s da classe %s
  12 +configuration-error-set-value=N\u00E3o \u00E9 poss\u00EDvel setar o valor %s no campo %s da classe %s
0 13 \ No newline at end of file
... ...
pom.xml
... ... @@ -72,6 +72,7 @@
72 72 <module>demoiselle-security-token</module>
73 73 <module>demoiselle-security-basic</module>
74 74 <module>demoiselle-security-jwt</module>
  75 + <module>demoiselle-configuration</module>
75 76 <!--<module>demoiselle-security-jwt</module>-->
76 77 </modules>
77 78  
... ...