For the ones who don’t know what OWNER library is, you can read my previous article here: Introducing OWNER, a tiny framework for Java Properties files.

It has been a long time since I posted the Part 1 of this article, and version 1.0.3 was just released. Now I should probably write about version 1.0.4 which is going to have some neat new features, and it will be released soon; but first I need to complete the overview of 1.0.3, and I will also mention new stuff coming in 1.0.4.

In February I wrote about variable expansion. Here I want to briefly introduce some other things that can be useful:

  • Advanced type conversion
  • LoadPolicies to load your property files using different strategies
  • Wrapping your java.util.Properties instances (and java.util.Maps too…)
  • Passing parameters to properties (and the wonder of @DefaultValue)

Advanced type conversion

In last post I showed that user can specify some basic Java types as return type for your Properties Mapping Interface, so this is what I am talking about:

public interface ServerConfig extends Config {
    Integer port();
    String hostname();
    @DefaultValue("42")
    int maxThreads();
    File logFile();
}

...
// then you can do
public static void main(String[] args) {
    ServerConfig cfg = ConfigFactory.create(ServerConfig.class);
    System.out.println("Server " + cfg.hostname() + ":" + cfg.port() + " will run " + cfg.maxThreads());
}

So if you have a file called ServerConfig.properties in the same package of the above class looking like the following one:

port=80
hostname=foobar.com
maxThreads=100
logFile=/var/log/myapp.log

The OWNER library will map the interface methods to the properties associated, making all the work to convert the String value to int, Integer, java.util.String or any other java primitive type, enums, wrapper classes. Plus some additional Java types are supported, like java.io.File, java.net.URL, java.net.URI, java.lang.Class etc. This is fully customizable, and fully documented here. But this is not new.

The new thing in version 1.0.3, is that you can map your property values to your own business objects. So, for instance, if you have class com.acme.wonderfulapp.User, you could define a properties mapping interface like this:

public interface MyAppConfig { 
    //... other properties
    @Key("admin.user") // this associates the key for the property
    User adminUser();
}

The associated file MyAppConfig.properties should define the above properties like:

# MyAppConfig.properties
admin.user=admin 

To allow the OWNER library to convert the property admin.user=admin to a com.acme.wonderfulapp.User there are many options:

  • You can declare a public Constructor taking java.lang.String or java.lang.Object:
    public class User {
        //...
    
        public User(String username) {
            this.username = username;
        }
    
        // or 
        public User(Object username) {
            this.username = username;
        }
    }
    
  • You can declare a public static method valueOf(String):
    public class User {
        //...
        public static User valueOf(String username) {
            User user = new User();
            user.setUsername(username);
            return user;
        }
    }
    
  • You can define and register a PropertyEditor
    public interface MyAppConfig extends Config {
        @DefaultValue("admin")
        User user();
    }
    
    public class UserPropertyEditor extends PropertyEditorSupport {
        @Override
        public void setAsText(String text) throws IllegalArgumentException {
            User user = new User();
            user.setUsername(text);
            setValue(user);
        }
    }
    
    public static void main(String[] args) {
        // register the propertyEditor (consult PropertyEditorManager javadocs)
        PropertyEditorManager.registerEditor(User.class, UserPropertyEditor.class);
        MyAppConfig cfg = ConfigFactory.create(MyAppConfig.class);
        System.out.println("admin username is: " + cfg.user().getUsername());
    }
    

    PropertyEditorManager resolves the editor using some naming convention, so if I rename the class UserPropertyEditor into UserEditor then there is no need to invoke the method PropertyEditorManager.registerEditor(): PropertyEditorManager tries to resolve an editor appending '-Editor' to your class name. The editor resolution is a little bit more complex than that; please consult PropertyEditorManager javadoc.

In the above examples I just showed simple cases, but having the ability specify the logic to convert a java property value into a domain object can be quite handy and powerful I think.
The full list of supported automatic conversion is available on the documentation website (see paragraph “Type Conversion”).

WARNING: SPOILER ALERT!

With version 1.0.4 the OWNER library also supports arrays and Collections of all the already supported types. This topic will be covered in the next post, when version 1.0.4 will be officially released. So you could do:

public interface MyAppConfig extends Config {
    @DefaultValue("admin,root")
    List<User> users(); // collections of user's domain classes
    @DefaultValue("8080,8090")
    int[] ports(); // also arrays and primitive are supported!
}

public static void main(String[] args) {
    PropertyEditorManager.registerEditor(User.class, UserPropertyEditor.class);

    MyAppConfig cfg = ConfigFactory.create(MyAppConfig.class);
    List<User> users = cfg.users();
    assertEquals("admin", users.get(0).getUsername());
    assertEquals("root", users.get(1).getUsername());
}

Thanks goes to Dmytro Chyzhykov for the awesome code he contributed.

But… I’ll tell you more after the 1.0.4 release.

LoadPolicies to load your property files using different strategies

By default you don’t specify any source, OWNER API tries to load the properties from a file called like your class: if my class is called com.acme.MyAppConfig, then by default OWNER will try to load com.acme.MyAppConfig.properties from the classpath.

Of course this is not enough, and since the first release, OWNER is able to load your property files in a flexible way. You can specify the annotation @Source with several locations, in form of URLs, from where the properties must be loaded.

@Sources({ "file:~/.myapp.config", "file:/etc/myapp.config", "classpath:foo/bar/baz.properties" })
public interface ServerConfig extends Config {
    public String myProperty();
    ...

This means that OWNER will fist try to load the file “.myapp.config” located in my home directory (the ‘~’ is resolved as the user home), then if that file does not exists the file “/etc/myapp.conf” will be loaded, and as last resort the file “foo/bar/baz.properties” will be loaded from the class path. And, as if this was not enough, you can also specify a default value for every property with the annotation @DefaultValue.
All this is nice, but NOT NEW, since version 1.0.3 adds even more.

In fact, with version 1.0.3 you have two different “load policies”. The default one, is the one exposed above. And we call this behavior LoadType.FIRST, since the first available resource specified by the @Source annotation is loaded and the remaining are ignored.
The new additional load policy is called LoadType.MERGE, and here is an example:

@LoadPolicy(LoadType.MERGE)
@Sources({ "file:~/.myapp.config", "file:/etc/myapp.config", "classpath:foo/bar/baz.properties" })
public interface ServerConfig extends Config {
    public String myProperty();
    ...

This means that, when I call the method ServerConfig.myProperty() ALL the sources will be considered in order. If myProperty is specified in “file:/etc/myapp.config” will be loaded from there, unless it is redefined in “file:~/.myapp.config”. So basically we have a kind of “fallback” mechanism which allows the user to have a main configuration with lower priority (i.e. system level) and overriding configurations with higher priorities (i.e. user level).

Wrapping your java.util.Properties instances (and java.util.Maps too…)

If you have existing instances of Properties (or Maps), for example you have a legacy application that loads the property in some particular way, or just instantiates those properties programmatically, you can “wrap” those instances with OWNER.
I called this mechanism “importing properties” since it is an *addition* to loading the properties from external resources, and you can use this feature in combination of it.
For instance you can wrap System.getProperties() or System.getenv() doing something like this:


// wrapping System.getenv() 

interface EnvProperties extends Config {
    @Key("HOME")
    String home();

    @Key("USER")
    String user();
}

public static void main(String[] args) {
    SystemEnvProperties cfg = ConfigFactory.create(SystemEnvProperties.class, System.getenv());
    assertEquals(System.getenv().get("HOME"), cfg.home());
    assertEquals(System.getenv().get("USER"), cfg.user());
}

// wrapping System.getProperties()

interface SystemEnvProperties extends Config {
    @Key("file.separator")
    String fileSeparator();

    @Key("java.home")
    String javaHome();
}

public static void main(String[] args) {
    SystemEnvProperties cfg = ConfigFactory.create(SystemEnvProperties.class, System.getProperties());
    assertEquals(File.separator, cfg.fileSeparator());
    assertEquals(System.getProperty("java.home"), cfg.javaHome());
}

// you can also wrap them together in a single interface:

interface SystemEnvProperties extends Config {
    @Key("file.separator")
    String fileSeparator();

    @Key("java.home")
    String javaHome();

    @Key("HOME")
    String home();

    @Key("USER")
    String user();
}

public static void main(String[] args) {
    SystemEnvProperties cfg = ConfigFactory.create(SystemEnvProperties.class, System.getProperties(), System.getenv());
    assertEquals(File.separator, cfg.fileSeparator());
    assertEquals(System.getProperty("java.home"), cfg.javaHome());
    assertEquals(System.getenv().get("HOME"), cfg.home());
    assertEquals(System.getenv().get("USER"), cfg.user());
}

In the same way, you can wrap any instance of Properties – and any subclass of java.util.Map – you already have in your project, in a convenient JavaBean-like interface (if you like JavaBeans), with the advantage of having the type conversion for free.

WARNING

Notice that the wrapped object becomes unmodifiable (since it is internally copied), so if you modify the original objects, the change will not be reflected. The objects instantiated by OWNER, should be considered immutable (well, I am planning to implement the “hot reloading” soon, maybe already in 1.0.4, and being thread safe is a requirement).

Passing parameters to properties (and the wonder of @DefaultValue)

This feature is not really new and is more related to internationalization than on configuration. In any case, in Java interface method can accept parameters and I thought it may be useful to parametrize a property value. And it was easy and elegant to implement, so I did it.

interface Sample extends Config {
    @DefaultValue("Hello Mr. %s!")
    String helloMr(String name);
}
public static void main(String[] args) {
    Sample sample = ConfigFactory.create(Sample.class);
    // this will print "Hello Mr. Luigi!"
    System.out.println(sample.helloMr("Luigi")); 
}

This is not new in 1.0.3 but I feel like this is an unknown feature, and… in the above example I also show the usage of the @DefaultValue annotation. Sometime you just want to define some default values for your configuration, plus specifying a source from where to load the overriding values defined by the user. This is one of the things I like more about this library: it saves me to write properties file until they are really needed. Or if you want, you can write an example, put it on the classpath and let the user to override it placing the property in a conventional location.
The parameters, are just a plus and an excuse to talk again about @DefaultValue.

WARNING: SPOILER ALERT!

Some people don’t need this parameter formatting feature, or the variable expansion that has been introduced in the previous episode. For example, your application may already implement variable expansion in a very similar way, and having an unwanted feature may lead to conflict with legacy features if you are trying to introduce OWNER in your project. For this reason, version 1.0.4 will have an annotation @DisableFeature which will allow the user to disable the variable expansion or the parameter formatting on class level and/or on method level.

Conclusions

This is just a quick tour on the new things regarding the OWNER library. In 1.0.3 there were also some minor bugfixes and some code cleanup.
What we are trying to do with OWNER library, is to add beautiful features while keeping the code compact, elegant, documented and thoroughly tested. So far so good, I suppose. And more things are bubbling in the pan*.

*: “what’s bubbling in the pan?” is the italian literal translation of “Cosa bolle in pentola?”, which corresponds to “What’s cooking?”


4 Responses to “OWNER 1.0.3 what’s new. Part 2.”  

  1. 1 benas

    Hi Luigi,

    Very nice library! Congratulations.

    I know others libraries that do basically the same thing as OWNER :

    https://github.com/thedarr/java-properties

    https://github.com/dbaeli/ez18n

    They seem to be not maintained anymore. But I do believe that OWNER is richer in terms of features! Good job.

    Nevertheless, IMO, there is a little problem with your approach (and GWT’s one too) :
    What should I do if my properties file contains dozens of properties? Should I write a method per property to bind it?
    And what if I have many properties file, which is frequent in production applications, I need to create a Java interface for each one? I’ll find my self writing a lot of code just for configuration sink, do you agree?

    Best regards!
    Benas

  2. 2 Luigi

    Hi Benas, thanks for your comment.

    I was unaware of those two. When I found myself parsing a property file and mapping it to a Java class, I tried to find if something was already available and I didn’t find anything:

    http://stackoverflow.com/questions/13861005/java-properties-file-binding-to-java-interface

    So I decided to write something good enough by myself. After some time I found other libraries (I think other two) implementing the same idea, on github, but the damage was already made.

    If your application has many properties or many properties files, I think that the best approach is to have something that saves you to deal with properties file loading, type converting and at the same time grants you freedom on the approach you want to follow. You can have a single huge properties file and many mapping classes, or you can have many properties files and just few mapping classes: both things are achievable with OWNER (I think I’ll write a post with some examples).

    You may want to have some structuring, to divide-and-conquer if the amount of your configuration is huge, I am thinking to this problem too:
    – first I thought that you could create sub-sections of a huge properties file, defining a hierarchical interface as I explained here: https://github.com/lviggiano/owner/issues/2
    – but recently I found very easy to turn the problem on the other way round: having many properties organized into an interface hierarchy: https://github.com/lviggiano/owner/blob/nested-configs/src/test/java/org/aeonbits/owner/NestedConfigTest.java

    At the moment I have no clear idea on which solution is best to give structure and make properties-based configuration to scale in volume. And this is why this stuff is experimental. So this problem needs to be studied. Maybe having both approaches is desirable, but properties are beautiful since they are easy, and I would like to keep a good balance between features and complexity.

    Anyway, I don’t think that if you have a big configuration in properties files, introducing this library will lead you in writing more code: it should be the opposite. I always see huge hand written Java wrapper classes over huge properties files, often with bugs and unused stuff.

    Properties are easy to read and manage. XML is often an unneeded complexity, and in particular I hate the huge spring based applicationConfig.xml that are all around, who are also mixing configuration with low level logic: it’s a huge mistake that I hope many people will realize.
    YAML and JSON can be used to give structure to a text based, human-friendly and non-verbose file format which I find much better than xml. In future, I would like to support yaml and json in this library.

    From the organization point of view, OWNER doesn’t force you to have a 1-to-1 mapping between properties files and mapping interfaces; so it’s up to the user to adopt what solution is best for his needs.

    Giving “structure” is also desirable, if you have some idea on how you would like this to be implemented from the user point of view, your comments will be more than welcome.
    We have a mailing list, which at the moment is not very used and we keep things to do in the github issues space. Feel free to bring your thoughts and ideas.

  3. 3 VK

    Firstly, congrats on a wonderful project. Best part is no dependencies.

    I am using the latest jar 1.0.4

    I am trying to load a config like this:
    @HotReload
    @Sources({“file:{gl_home}/config/teamsite.properties})
    interface SomeConfig extends Config, Reloadable, Mutable {

    }

    gl_home is a System property that is set to some path. But the file does not loads up. I even tried ConfigFactory.setProperty(), but gl_home is not parsed. When I replace gl_home with the full path, this works.

    Any idea what I am doing wrong here?

    Thanks,

  4. 4 Luigi

    Variables must be specified with ${variable} not just {variable}. Hope
    this helps.

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>



Calendar

May 2013
M T W T F S S
« Feb   Dec »
 12345
6789101112
13141516171819
20212223242526
2728293031  

Follow me

twitter flickr LinkedIn feed

Subscribe by email

Enter your email address:

Archives


Categories

Tag Cloud


Listening