Config-Builder

Motivation

ConverterConfig
DBConverter RuleConverter
CommandLineOptionsParser CommandLineOptionsLoader
ConfigPropertiesLoader PropertyLoader
ConfigDeterminer ConfigBuilder
ConfigManager ConfigProvider
PropertiesResourceLoader
BeanMerger
ConfigValidator

Features

    • key-value files
    • system properties
    • environment variables
    • command line
    • primitive types
    • arbitrary objects
    • collections
  • loading of values from various sources
  • "casting" of string values
  • "building" fully configured instances
  • completion of/merging with existing instances

@PropertyValue("user.name")
private String userName;
 

@SystemPropertyValue("user.name")
private String userName;
 

@EnvironmentVariableValue("PATH")
private String path;
 

@CommandLineValue(shortOpt="u", longOpt="user", hasArg="true")
private String userName;
 

Sounds nice,
but what does it take?

1. Create your class


public class TechDayConfig {
    
    private boolean isOpenTechDay;
    private int numberOfSteaks;
    private String location;
    private String userName;
    private Barbecue barbecue;
    private Collection<String> people;
    private Collection<Steak> steaks;
}
  

2. Annotate your class


@PropertiesFiles("techday")
public class TechDayConfig {

    ...

    @DefaultValue("false")
    @CommandLineValue(shortOpt="t", longOpt="test", hasArg=false)
    private boolean isOpenTechDay;

    @DefaultValue("3")
    @CommandLineValue(shortOpt="n", longOpt="steaks", hasArg=true)
    private int numberOfSteaks;

    @EnvironmentVariableValue("HOME")
    @PropertyValue("location")
    private String location;

    @SystemPropertyValue("user.name")
    private String userName;

    @TypeTransformer(BarbecueFactory.class)
    @PropertyValue("barbecue")
    private Barbecue barbecue;

    @PropertyValue("people")
    private Collection<String> people;

    @TypeTransformer(Steak.class)
    @PropertyValue("steaks")
    private Collection<Steak> steaks;
}
  

3. Optional: Configure the Property-Loader


@PropertyLocations(directories = {"/home/user"}, contextClassLoader = true)
@PropertySuffixes(extraSuffixes = {"tngtech","myname"}, hostNames = true)
public class TechDayConfig {
}
  

4. Optional: Validation


@NotEmpty("username.notEmpty")
private String userName;
  

@Validation
private void validate() {
    <...>
}
  

5. Optional: Non-Default-Constructor


public class TechDayConfig {
    public MyConfig(Barbecue barbecue) {
        this.barbecue = barbecue;
    }
}
  

How does it work?

  • load properties
  • parse command line options
  • find a suitable constructor
  • instantiate config object
  • set fields
  • validate

Small Footprint

  • Property-Loader
  • Apache CLI
  • Log4j
  • JSR303
  • Google Guava

The Property-Loader

Features

    • paths and URLs
    • classpath
    • relative to a class
    • ClassLoader
    • local hostames
    • username
    • custom Suffixes
    • resolving of variables
    • environment variables
    • system properties
    • file includes
    • encryption/decryption
  • hierarchial finding and loading of key-value files (properties/XML)
  • suffixes
  • postprocessing

key = {$nested{$variable}}
variable = test
nestedtest = value
 

key = $PATH
 

%include=file1,file2,file3
 

key = DECRYPT:value
 

How do you configure all this?

Search-Locations

    • classpath of current thread
    • home directory
    • current directory
  • default search locations
  • paths and URLs
  • classpath of current thread
  • ClassLoader
  • relative to a class

propertyLoader.atDefaultLocations()
 

propertyLoader.atDirectory(String directory)
propertyLoader.atBaseURL(URL url)
propertyLoader.atCurrentDirectory()
propertyLoader.atHomeDirectory()
 

propertyLoader.atContextClassPath()
 

propertyLoader.atClassLoader(ClassLoader classLoader)
 

propertyLoader.atRelativeToClass(Class<?> clazz) 
 

Suffixes

    • local hostnames
    • username
    • 'override'
  • default suffixes
  • local hostnames
  • username
  • custom suffixes

propertyLoader.addDefaultSuffixes()
 

propertyLoader.addLocalHostNames()
 

propertyLoader.addUserName()
 

propertyLoader.addSuffix(String directory)
propertyLoader.addSuffixList(List<String> suffixes)
 

Postprocessing

  • default filters: all available
  • variable resolving
  • environment variables
  • decryption
  • warnings

propertyLoader.withDefaultFilters() 
 

propertyLoader.withVariableResolvingFilter() 
 

propertyLoader.withEnvironmentResolvingFilter() 
 

propertyLoader.withDecryptingFilter()
 

propertyLoader.withWarnIfPropertyHasToBeDefined() 
propertyLoader.withWarnOnSurroundingWhitespace() 
 

Java Magic

  • Annotations
  • Reflections

Annotations


@ValueExtractorAnnotation(CommandLineValueProcessor.class)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CommandLineValue {
    String shortOpt();
    String longOpt();
    String description() default "";
    boolean hasArg() default false;
    boolean required() default false;
}
  

Limits And Solutions

no inheritance annotate annotations again
no implementation of
method bodies
AnnotationProcessor classes
limited selection of "attachment" types:
primitives, strings, classes, annotations and arrays
-
only constant "attachments" ?

Example Solution


Class<? extends IValueExtractorProcessor> processor;
for (Annotation annotation :  ) {
    processor = annotation.annotationType().getAnnotation(ValueExtractorAnnotation.class).value();
    value = configBuilderFactory.getInstance(processor).getValue(annotation, builderConfiguration);
    if (value != null) {
        break;
    }
}
  

Reflections

    • retrieve fields of a class
    • retrieve methods and constructors of a class
    • retrieve annotations
    • instantiate objects
    • retrieve the value of variables
    • set variables
    • change private attributes
  • retrieve properties of objects, classes, fields and methods at runtime
  • instantiate and manipulate objects
  • call methods

Class<?> clazz = obj.getClass();

Field[] publicFields = clazz.getFields();
Field[] allFields = clazz.getDeclaredFields();

Field userNameField = clazz.getField("userName");

final class java.lang.reflect.Field
extends AccessibleObject
implements Member
 

Class<?> clazz = ...

Method[] publicMethods = clazz.getMethods();
Method[] allMethods = clazz.getDeclaredMethods();

Method getValue = clazz.getMethods("getValue", Class[] args);

Constructor[] constructors = clazz.getConstructors();
Constructor<T> getConstructor(Class... parameterTypes)throws NoSuchMethodException

final class java.lang.reflect.Method
extends AccessibleObject
implements GenericDeclaration, Member

final class java.lang.reflect.Constructor<T>
extends AccessibleObject
implements GenericDeclaration, Member
 

Annotation[] annotations = clazz.getAnnotations()
Annotation[] annotations = clazz.getDeclaredAnnotations()
Annotation[] annotations = field.getDeclaredAnnotations()
Annotation[] annotations = method.getDeclaredAnnotations()
Annotation[] annotations = annotation.getDeclaredAnnotations()

Annotation annotation = clazz.getAnnotation(PropertyValue.class)
 

Object newObj = clazz.newInstance();
Object newObj = constructor.newInstance(args);
 

Object newObj = field.get();
String fieldName = field.getName();
Object fieldType = field.getType();
 

field.set(Object o);
 

private ...

field.setAccessible(true);
method.setAccessible(true);
 

Method method = o.getClass().getDeclaredMethod("methodName");
Method method = o.getClass().getDeclaredMethod("methodName", Object[] args);

method.setAccessible(true);

method.invoke(Object o);
method.invoke(Object o, Object[] args);
method.invoke(null);
 

Open Source

https://github.com/TNG/config-builder

https://github.com/TNG/property-loader

Maven Central

<dependency>
    <groupId>com.tngtech.java</groupId>
    <artifactId>config-builder</artifactId>
    <version>1.0</version>
</dependency>
<dependency>
    <groupId>com.tngtech.java</groupId>
    <artifactId>property-loader</artifactId>
    <version>1.1</version>
</dependency>