Reinventing java properties or using “Owner” library

Most of java developers use property files in their daily work. Basically we need them to configure our applications, to make our code more flexible and reusable. Unfortunately Java API for properties is very far away from perfect also most of developers are used to writing lots of the repetitive and routine code when accessing properties.

What do I mean? Let’s consider the following example:

foo.properties

db.timeout=10

Bar.java

// static fields
private static final String TIMEOUT_KEY = "db.timeout";
private static final int DEFAULT_TIMEOUT = 10;
..
// within some method
String timeOutString = properties.getProperty(TIMEOUT_KEY);
Integer timeOut;
try {
    if(timeOutString != null &&  !timeOutString.isEmpty()) {
        timeOut = Integer.parseInt(timeOutString);
    } else {
        timeOut = DEFAULT_TIMEOUT;
    }
} catch (NumberFormatException e){
    timeOut = DEFAULT_TIMEOUT;
}
// using timeout

So in the example above we can see classical issues with accessing properties.

Issue #1 – Duplication of converting logic

try {
    timeOut = Integer.parseInt(timeOutString);
} catch (NumberFormatException e){
    timeOut = DEFAULT_TIMEOUT;
}

This try-catch is going to be placed in every place where you want to convert String to Integer, which leads to duplication. So if Integer property was changed to Long you would have to change it in all places where you use it.

Issue #2 – Duplication of fallback to default value

if(timeOutString != null && !timeOutString.isEmpty()) {
    timeOut = Integer.parseInt(timeOutString);
} else {
    timeOut = DEFAULT_TIMEOUT;
}

Unfortunately, I’ve seen many times a piece of code like one written above. Sometimes property value is being checked just for null, sometimes for emptiness, sometimes fallback value is used after unsuccessful conversion. In all cases same mechanism:

no effective value – use default

Issue #3 – Duplication of constants

private static final String TIMEOUT_KEY = "db.timeout";
private static final int DEFAULT_TIMEOUT = 10;

I guess most of you have ever seen such constants. Unfortunately, I faced many situation when constants were not extracted to a separate class and just were copy-pasted. So when we have service-A which uses property1,property2 and service-B which uses property1, property2 we would have 16 string constants.

  • 4 constants in service-A impl (prop1_key, prop1_default, prop2_key,prop2_default)
  • 4 constants in service-A test (prop1_key, prop1_default, prop2_key,prop2_default)
  • same for service-B impl
  • same for service-B test

Can you imagine how ineffective it is?

At this moment you may think that I’ve never heard about properties support in the spring framework or in Apache framework , but it is not true. I am aware of them and I am using them, but still they don’t solve issues mentioned above.  @Value annotation allows you to inject property value on context-startup and there is no ease way to reload injected properties ( I am aware only about spring-cloud). Apache framework provides lots of methods for conversion and mechanism of fallback to default, but still we need to duplicate constants which sometimes looks stupid, especially if you copy-them in your Junit test.

Here is  the moment when Owner framework enters the game. Basically it makes properties handling as ease as possible. This is how example above looks like using Owner:

    import org.aeonbits.owner.Config;
    public interface AppConfig extends Config {
        @DefaultValue("10")
        @Key("db.timeout")
        int dbTimeout();
    }
// somewhere in a method
ServerConfig cfg = ConfigFactory.create(ServerConfig.class);
int timeout = cfg.dbTimeout();
//using timeout

Is not that cool? The general idea is that we are accepting convention over configuration which allows us to write as less code as possible. Also annotations in the library lead us to more declarative style rather than imperative.

Instead of describing Owner documentation , which is good enough, I would prefer to look at some example.

First of all we need to create maven project, I created one using
mvn archetype:generate + maven-archetype-quickstart

Then we need to add owner library via dependency

    <dependencies>
        <dependency>
            <groupId>org.aeonbits.owner</groupId>
            <artifactId>owner-java8</artifactId>
            <version>1.0.6</version>
        </dependency>
    </dependencies>

Then I created 2 properties file. One is called resources/db.properties and the second one is resources/config/server.properties. I put the following content there:

// db.properties
db.url=jdbc:mysql://db.domain.com/mysql
dbTimeout=10:SECONDS

//server properties
server.usernames=thor,loki
server.blog.url=https://nikitapavlenko.wordpress.com/

Then we can create config class. I created interface called AppConfig.

@LoadPolicy(Config.LoadType.MERGE)
@Sources({"classpath:db.properties", "classpath:config/server.properties"})
interface AppConfig extends Config {

    @DefaultValue("10")
    int maxThreads();

    @Key("db.url")
    String databaseConnectionUrl();

    @ConverterClass(TimeoutConverter.class)
    TimeOut dbTimeout();

    @Separator(",")
    @Key("server.usernames")
    List<String> serverUsernames();

    @Key("server.blog.url")
    URL serverBlogUrl();

}

Without @Sources annotation owner tries to find ${configclassname}.properties file in a classpath. Since I named files in another I can say owner need to load my properties files via @Sources annotation:

@LoadPolicy(Config.LoadType.MERGE)
@Sources({"classpath:db.properties", "classpath:config/server.properties"})

In source you can use classpath resources as well as just file resources (e.g. “file:/home/foo.properties”)

Lets go through define methods one by one:

  • maxThreads – no property with such name is defined, default value from annotation should be used
  • databaseConnectionUrl – no property with such name, but there is @Key annotation which will be used to get correct value
  • dbTimeout – is existing property, but class timeout was defined by me, so custom Converter which I defined in @ConverterClass(TimeoutConverter.class) should be used
    
    public class TimeOut {
        private int amount;
        private TimeUnit timeUnit;
    
        public TimeOut(int amount, TimeUnit timeUnit
        ) {
            this.amount = amount;
            this.timeUnit = timeUnit;
        }
    
        public int getAmount() {
            return amount;
        }
    
        public TimeUnit getTimeUnit() {
            return timeUnit;
        }
    }
    
    public class TimeoutConverter implements Converter {
    
        @Override
        public Object convert(Method method, String s) {
            String[] values = s.split(":");
            int amount = Integer.parseInt(values[0]);
            TimeUnit timeUnit = TimeUnit.valueOf(values[1]);
            return new TimeOut(amount, timeUnit);
        }
    }
    
    
  • serverUsernames – such property does not exist, but again there is @Key annotation and @Separator, which means that values will be splitted using separator
  • serverBlogUrl – example of rich Convertion support, converting java.net.URL

Then I created a class ConfigDemo to show how to access defined properties.

public class ConfigDemo {

    private static void print(String value){
        System.out.println(value);
    }

    public static void main(String[] args) {
        AppConfig configApp = ConfigFactory.create(AppConfig.class);
        print("Max threads:" + configApp.maxThreads());
        print("DB URL: " + configApp.databaseConnectionUrl());
        print("Timeout " + configApp.dbTimeout().getAmount() + " " + configApp.dbTimeout().getTimeUnit());
        print("Usernames: " + configApp.serverUsernames());
        print("URL: " + configApp.serverBlogUrl());
    }

}

Here is the output of the program.

Max threads:10
DB URL: jdbc:mysql://db.domain.com/mysql
Timeout 10 SECONDS
Usernames: [thor, loki]
URL: https://nikitapavlenko.wordpress.com/

You can find this example on my github: owner-properties-demo.

As you see, it works pretty well. Now you can go much faster with your app configuration since there is no need to write dozens of lines to access , fallback or convert properties. All of that is already there.

Summary

If you are tired of writing routine and repetitive code for handling properties then owner it is something you should definitely try to use. You can be sure that is it stable, at least they use TDD and claim 97% code coverage. Also they are feature rich, which means that you will definitely find what you need.  In regards to me: if I need properties management in my next pet or production project  I will definitely use owner library.

Advertisements

Receive notification in the system tray on server startup (Tomcat, Hybris)

On many large e-commerce projects server startup can take up to 6-10 minutes. Hybris projects I worked at are not exception. So usually when I start the tomcat server I can do lots of other things. e.g. reading the spec, browsing the net or even continue development in my IDE. So after 5-6 minutes I can forget about the fact that my server is starting up. So I thought that it would be great to have a tool that can notify me as soon as tomcat starts up and I decided to develop  it. So basically what the tool has to do is to understand when tomcat server finishes startup and call OS utility to notify me about it.

OOTB hybris uses  Growl to notify developer about init/update actions. To activate it you need to install Growl for windows or Grow for OS X and add it your PATH.

Unfortunately Hybris does not support “server is ready” notification. But there is very simply way to do that. First of all tomcat has The LifeCycle Listener Component that can let us know about different events in tomcat server.

public interface Lifecycle {
	String BEFORE_INIT_EVENT = "before_init";
	String AFTER_INIT_EVENT = "after_init";
	String START_EVENT = "start";
	String BEFORE_START_EVENT = "before_start";
	String AFTER_START_EVENT = "after_start";
	String STOP_EVENT = "stop";
	String BEFORE_STOP_EVENT = "before_stop";
	String AFTER_STOP_EVENT = "after_stop";
	String AFTER_DESTROY_EVENT = "after_destroy";
	String BEFORE_DESTROY_EVENT = "before_destroy";
	String PERIODIC_EVENT = "periodic";
	String CONFIGURE_START_EVENT = "configure_start";
	String CONFIGURE_STOP_EVENT = "configure_stop";
}

From the list above AFTER_START_EVENT is the most appropriate event we can listen to. OK, it clear about event, but how should we put notification in the system tray? Basically there are two options:

  1. Use Growl via command line tool as Hybris does
  2. Use Java’s SystemTray class

As a good developer I think we should support both versions. So when Growl exists – use it, if does not exist – use default system tray. Lets write our implementation.

Step 1 – Create a project that can be packed as jar

As maven is still the most popular build tool lets create project from maven archetype.

mvn archetype:generate -DgroupId=com.wordpress.nikitapavlenko
-DartifactId=tomcat-notification
-DarchetypeArtifactId=maven-archetype-quickstart
-DinteractiveMode=false

My maven pom.xml is following:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.wordpress.nikitapavlenko</groupId>
    <artifactId>tomcat-notification</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>tomcat-notification</name>
    <url>http://maven.apache.org</url>
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-catalina</artifactId>
            <version>7.0.12</version>
        </dependency>
    </dependencies>
</project>

Step 2 – Implement org.apache.catalina.LifecycleListener

package com.wordpress.nikitapavlenko;

import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;

import java.awt.*;
import java.awt.TrayIcon.MessageType;
import java.io.IOException;
import java.net.URL;
import java.util.Calendar;
import java.util.Map;

public class TomcatListener implements LifecycleListener {

    @Override
    public void lifecycleEvent(LifecycleEvent event) {
        if (Lifecycle.AFTER_START_EVENT.equals(event.getType())) {
            if (growlExist()) {
                notifyViaGrowl();
            }
            else {
                notifyViaDefaultSystemTray();
            }
        }
    }

    private void notifyViaGrowl() {
        try {
            String message = "\"Server successfully started. [" + Calendar.getInstance().getTime()+"]\"";
            Runtime.getRuntime().exec("growlnotify /t:Tomcat " + message);
            System.out.println("Growl notification sent");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private boolean growlExist() {
        Map<String, String> env = System.getenv();
        String path = env.get("Path");
        return path != null && path.toLowerCase().contains("growl");
    }

    private void notifyViaDefaultSystemTray() {
        SystemTray tray = SystemTray.getSystemTray();
        TrayIcon trayIcon = createTrayIcon();
        try {
            tray.add(trayIcon);
            trayIcon.displayMessage("Tomcat", "Server successfully started.", MessageType.INFO);
            System.out.println("System tray notification sent");
        } catch (AWTException e) {
            e.printStackTrace();
        }
    }

    private TrayIcon createTrayIcon() {
        URL resource = getClass().getClassLoader().getResource("icon.png");
        Image image = Toolkit.getDefaultToolkit().createImage(resource);
        TrayIcon trayIcon = new TrayIcon(image, "Tomcat");
        trayIcon.setImageAutoSize(true);
        trayIcon.setToolTip("Hybris server");
        return trayIcon;
    }

}

Step 3 – Package sources as jar file

mvn clean package

At this moment you will have an artifact “tomcat-notification-1.0-SNAPSHOT.jar”

Step 4 – Register listener in the server.xml

For the based hybris app open config/tomcat/conf/server.xml and add a new listener there:

...
<Listener className="com.wordpress.nikitapavlenko.TomcatListener" context="GenericJavaBeanResource"/>
...

Step 5 – Add our jar to tomcat lib folder

For the hybris based project add this jar to config/customize/platform/tomcat/lib and don’t forget to run customize target before ant all.

Step 6 – Results

When growl is in system’s Path:
growl.png

When growl is not in the system’s Path:

default.png

The source code is available my Github. Thanks for reading!