Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New @Config.Prefix annotation. #273

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion owner/src/main/java/org/aeonbits/owner/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,8 @@ enum HotReloadType {
*/
enum DisableableFeature {
VARIABLE_EXPANSION,
PARAMETER_FORMATTING
PARAMETER_FORMATTING,
PREFIX
}

/**
Expand Down Expand Up @@ -367,4 +368,14 @@ enum DisableableFeature {
Class<? extends Preprocessor>[] value();
}

/**
* Specifies simple <code>{@link String}</code> as prefix to properties names.
*/
@Retention(RUNTIME)
@Target({TYPE})
@Documented
@interface Prefix {
String value();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import static org.aeonbits.owner.Converters.SpecialValue.NULL;
import static org.aeonbits.owner.Converters.convert;
import static org.aeonbits.owner.PreprocessorResolver.resolvePreprocessors;
import static org.aeonbits.owner.PropertiesMapper.key;
import static org.aeonbits.owner.util.Util.isFeatureDisabled;
import static org.aeonbits.owner.util.Reflection.invokeDefaultMethod;
import static org.aeonbits.owner.util.Reflection.isDefault;
Expand Down Expand Up @@ -80,7 +79,7 @@ private Object resolveProperty(Method method, Object... args) {

// TODO: this if should go away! See #84 and #86
if (value == null && !isFeatureDisabled(method, VARIABLE_EXPANSION)) {
String unexpandedKey = key(method);
String unexpandedKey = propertiesManager.propertiesMapper().key(method);
value = propertiesManager.getProperty(unexpandedKey);
}
if (value == null)
Expand All @@ -103,7 +102,7 @@ private String preProcess(Method method, String value) {
}

private String expandKey(Method method, Object... args) {
String key = key(method);
String key = propertiesManager.propertiesMapper().key(method);
if (isFeatureDisabled(method, VARIABLE_EXPANSION))
return key;
return substitutor.replace(key, args);
Expand Down
19 changes: 16 additions & 3 deletions owner/src/main/java/org/aeonbits/owner/PropertiesManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@

import static java.util.Collections.synchronizedList;
import static org.aeonbits.owner.Config.LoadType.FIRST;
import static org.aeonbits.owner.PropertiesMapper.defaults;
import static org.aeonbits.owner.util.Util.*;

/**
Expand All @@ -38,6 +37,9 @@
* @author Luigi R. Viggiano
*/
class PropertiesManager implements Reloadable, Accessible, Mutable {

private static final long serialVersionUID = 0L;

private final Class<? extends Config> clazz;
private final Map<?, ?>[] imports;
private final Properties properties;
Expand All @@ -64,6 +66,8 @@ class PropertiesManager implements Reloadable, Accessible, Mutable {
* Reflection is slow.
*/
private Map<Method, Decryptor> encryptedKeys = new HashMap<Method, Decryptor>();

private PropertiesMapper propertiesMapper;

final List<PropertyChangeListener> propertyChangeListeners = synchronizedList(
new LinkedList<PropertyChangeListener>() {
Expand All @@ -90,10 +94,15 @@ public boolean remove(Object o) {
ConfigURIFactory urlFactory = new ConfigURIFactory(clazz.getClassLoader(), expander);
uris = toURIs(clazz.getAnnotation(Sources.class), urlFactory);

PropertiesMapper.Builder builder = PropertiesMapper.builder().apply(clazz);

for (Class<?> inter : clazz.getInterfaces()) {
this.uris.addAll(toURIs(inter.getAnnotation(Sources.class), urlFactory));
builder.apply(inter);
}

propertiesMapper = builder.build();

LoadPolicy loadPolicy = clazz.getAnnotation(LoadPolicy.class);
if (loadPolicy == null) {
for (Class<?> inter : clazz.getInterfaces()) {
Expand Down Expand Up @@ -141,7 +150,7 @@ public void run() {
// Reflection is slow, so we will cache all methods with EncryptedValue annotation.
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (PropertiesMapper.isEncryptedValue(method)) {
if (propertiesMapper.isEncryptedValue(method)) {
EncryptedValue encriptedKey = method.getAnnotation(EncryptedValue.class);
decryptorClazz = encriptedKey.value();
if (decryptorClazz != IdentityDecryptor.class) {
Expand All @@ -153,6 +162,10 @@ public void run() {
}
}

PropertiesMapper propertiesMapper() {
return propertiesMapper;
}

/**
* If method contains the EncryptedValue annotation it Decrypts the value with the associated {@link Decryptor}.
*
Expand Down Expand Up @@ -208,7 +221,7 @@ Properties load() {
private Properties load(Properties props) {
try {
loading = true;
defaults(props, clazz);
propertiesMapper.applyDefaults(props, clazz);
Properties loadedFromFile = doLoad();
merge(props, loadedFromFile);
merge(props, reverse(imports));
Expand Down
117 changes: 84 additions & 33 deletions owner/src/main/java/org/aeonbits/owner/PropertiesMapper.java
Original file line number Diff line number Diff line change
@@ -1,52 +1,103 @@
/*
* Copyright (c) 2012-2015, Luigi R. Viggiano
* All rights reserved.
*
* This software is distributable under the BSD license.
* See the terms of the BSD license in the documentation provided with this software.
*/

package org.aeonbits.owner;

import static org.aeonbits.owner.Config.DisableableFeature.PREFIX;
import static org.aeonbits.owner.util.Util.isFeatureDisabled;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.aeonbits.owner.Config.DefaultValue;
import org.aeonbits.owner.Config.EncryptedValue;
import org.aeonbits.owner.Config.Key;
import org.aeonbits.owner.Config.Prefix;

import java.lang.reflect.Method;
import java.util.Properties;
class PropertiesMapper implements Serializable {

private static final long serialVersionUID = 0L;

private static class MethodInfo implements Serializable {

private static final long serialVersionUID = 0L;

private String key;
private String prefix;
private String originalKey;
private String defaultValue;
private boolean isEncryptedValue;

private MethodInfo(Prefix prefixAnnotation, Method method) {

DefaultValue defaultAnnotation = method.getAnnotation(DefaultValue.class);
defaultValue = defaultAnnotation == null ? null : defaultAnnotation.value();

Key keyAnnotation = method.getAnnotation(Key.class);
originalKey = keyAnnotation == null ? method.getName() : keyAnnotation.value();

prefix = "";
if (prefixAnnotation != null && !isFeatureDisabled(method, PREFIX)) {
prefix = prefixAnnotation.value();
}

/**
* Maps methods to properties keys and defaultValues. Maps a class to default property values.
*
* @author Luigi R. Viggiano
*/
final class PropertiesMapper {
key = prefix + originalKey;

/** Don't let anyone instantiate this class */
private PropertiesMapper() {}
isEncryptedValue = method.getAnnotation(EncryptedValue.class) != null;
}

private boolean existsDefault() {
return defaultValue != null;
}
}

static boolean isEncryptedValue( Method method ) {
return ( method.getAnnotation( EncryptedValue.class ) ) != null;
private Map<String, MethodInfo> methods;

private PropertiesMapper(Map<String, MethodInfo> methods) {
this.methods = methods;
}

static String key(Method method) {
Key key = method.getAnnotation(Key.class);
return (key == null) ? method.getName() : key.value();
boolean isEncryptedValue(Method method) {
return methods.get(method.toString()).isEncryptedValue;
}

static String defaultValue(Method method) {
DefaultValue defaultValue = method.getAnnotation(DefaultValue.class);
return defaultValue != null ? defaultValue.value() : null;
String key(Method method) {
return methods.get(method.toString()).key;
}

static void defaults(Properties properties, Class<? extends Config> clazz) {
Method[] methods = clazz.getMethods();
for (Method method : methods) {
String key = key(method);
String value = defaultValue(method);
if (value != null)
properties.put(key, value);
void applyDefaults(Properties properties, Class<?> clazz) {
Method[] clazzMethods = clazz.getMethods();
for (Method method : clazzMethods) {
MethodInfo info = methods.get(method.toString());
if (info.existsDefault()) {
properties.put(info.key, info.defaultValue);
}
}
}

static Builder builder() {
return new Builder();
}

static class Builder {

private Map<String, MethodInfo> methods;

private Builder() {
methods = new HashMap<String, MethodInfo>();
}

Builder apply(Class<?> clazz) {
Prefix prefixAnnotation = clazz.getAnnotation(Prefix.class);
for (Method method : clazz.getMethods()) {
methods.put(method.toString(), new MethodInfo(prefixAnnotation, method));
}
return this;
}

PropertiesMapper build() {
return new PropertiesMapper(Collections.unmodifiableMap(methods));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package org.aeonbits.owner.prefix;

import static org.aeonbits.owner.Config.DisableableFeature.*;
import static org.aeonbits.owner.util.Collections.map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

import org.aeonbits.owner.Config;
import org.aeonbits.owner.ConfigFactory;
import org.aeonbits.owner.Config.*;
import org.junit.Test;

public class PrefixKeyExpansionTest {

@Sources("classpath:org/aeonbits/owner/variableexpansion/KeyExpansionExample.xml")
@Prefix("servers.${env}.")
public interface MyConfig extends Config {

String name();

String hostname();

Integer port();

String user();

@DisableFeature(VARIABLE_EXPANSION)
String password();
}

@Test
public void testKeyExpansion() {
MyConfig cfg = ConfigFactory.create(MyConfig.class, map("env", "dev"));

assertEquals("DEV", cfg.name());
assertEquals("devhost", cfg.hostname());
assertEquals(new Integer(6000), cfg.port());
assertEquals("myuser1", cfg.user());
assertNull(cfg.password()); // expansion is disabled on method level
}

@DisableFeature(VARIABLE_EXPANSION)
@Sources("classpath:org/aeonbits/owner/variableexpansion/KeyExpansionExample.xml")
@Prefix("servers.${env}.")
public interface MyConfigWithExpansionDisabled extends Config {

String name();

String hostname();

Integer port();

String user();

String password();
}

@Test
public void testKeyExpansionDisabled() {
MyConfigWithExpansionDisabled cfg = ConfigFactory.create(MyConfigWithExpansionDisabled.class, map("env", "dev"));

assertNull(cfg.name());
assertNull(cfg.hostname());
assertNull(cfg.port());
assertNull(cfg.user());
assertNull(cfg.password());
}

@Sources("classpath:org/aeonbits/owner/variableexpansion/KeyExpansionExample.xml")
@Prefix("servers.${env}.")
public interface ExpandsFromAnotherKey extends Config {

@DisableFeature(PREFIX)
@DefaultValue("dev")
String env();

String name();

String hostname();

Integer port();

String user();

@DisableFeature(VARIABLE_EXPANSION)
String password();
}

@Test
public void testKeyExpansionFromAnotherKey() {
ExpandsFromAnotherKey cfg = ConfigFactory.create(ExpandsFromAnotherKey.class);

assertEquals("DEV", cfg.name());
assertEquals("devhost", cfg.hostname());
assertEquals(new Integer(6000), cfg.port());
assertEquals("myuser1", cfg.user());
assertNull(cfg.password()); // expansion is disabled on method level
}

@Test
public void testKeyExpansionFromAnotherKeyWithImportOverriding() {
ExpandsFromAnotherKey cfg = ConfigFactory.create(ExpandsFromAnotherKey.class, map("env", "uat"));

assertEquals("UAT", cfg.name());
assertEquals("uathost", cfg.hostname());
assertEquals(new Integer(60020), cfg.port());
assertEquals("myuser2", cfg.user());
assertNull("mypass2", cfg.password()); // expansion is disabled on method level
}

@Sources("classpath:org/aeonbits/owner/variableexpansion/KeyExpansionExample.xml")
@Prefix("servers.${env}.")
public interface UseOfDefaultValueIfNotFound extends Config {

@DisableFeature(PREFIX)
@DefaultValue("dev")
String env();

@Config.Key("nonDefinedInSourceKey")
@Config.DefaultValue("wantedValue")
String undefinedPropInSource();

}

@Test
public void testKeyExpansionAndDefaultValue() {
UseOfDefaultValueIfNotFound cfg = ConfigFactory.create(UseOfDefaultValueIfNotFound.class);

assertEquals("dev", cfg.env());
assertEquals("wantedValue", cfg.undefinedPropInSource());
}
}
Loading