Channels ▼
RSS

JVM Languages

Type-Safe File-Based Configuration


This month, I'll finish up discussing configuration issues by showing you a type-safe, properties-file-based configuration system that's easily customized to your own needs.

More Insights

White Papers

More >>

Reports

More >>

Webcasts

More >>

I'm using all of the principles that I discussed on my first article on the subject Solving the Configuration Problem for Java Apps, the main one being that it should just work. Your code should not be cluttered with repetitive routines to detect or handle common errors.

In particular, I'm using an enum-based approach wherein every possible configuration-property key maps to an enum element, so it's impossible to misspell a key name; and I'm front loading error detection so you can use configuration properties without having to worry about things like type mismatches, missing or illegal values, unexpected exceptions, etc. Simply using any of the configuration properties in the file guarantees the following for all properties defined in the file:

  • The configuration file exists and is readable.
  • All expected configuration parameters are actually defined in the file.
  • All configuration parameters either have associated values or have a default value defined in the code.
  • All values are of the correct type (Integer, Long, File, URL, Date, etc. — any type with a String constructor works).
  • All values are "valid." Out-of-the box tests verify that a number or date is in range (actually, any class of object that implements Comparable can be checked for "in range"), or that a given value matches a specified regular expression, but you can also define your own load-time verification semantics.

Type safety is accomplished using Java's reflection APIs, so this month's code also demonstrates how to use reflection (at least in a small way).

If you haven't read my two prior articles on this topic: I discussed the basics of enum-based configuration in the first installment of this series, so you'll need to read that article before this one.

Also, I use the ExtendedLogger class from last month's Custom Configuring Java Apps: Extending log4j heavily in this month's code. ExtendedLoggers work like normal log4j loggers, except that they support printf()-like formatting and the log methods all return the log message, so you can easily recycle the message in an exception object.

Configuration Without The Pain

Let's start by looking at how you use the system. Here's a sample properties-based configuration file (which I called test.properties in my test code).

    key.string=Doo What Ditty
    key.int=10
    key.long=9223372036854775806
    key.double=1.23
    key.boolean=false
    key.location=/tmp/foo
    key.url=http://www.holub.com/index.html
    intBound=2
    phoneNumber=(510)555-1212
    intNoDefault=0
    strNoDefault=????

You can read these properties simply by using an enum called TestConfiguration (we'll look at it in depth in a moment). You don't need to load the file explicitly, check whether or not the keys exist, supply default values, convert the values to typed object, etc. All that busy work is done for you automatically.

You just access the value with a single like of code; like this:

    Integer i = TestConfiguration.key_int.intValue();

which evaluates to an Integer having the value 10, as I specified with the statement key.int=10 in test.properties. Similarly,

    URL u = TestConfiguration.key_url.URLValue()

evaluates to a URL object that has been initialized to "http://www.holub.com/index.html" Note that I've replaced the dots in the actual key names (in the file) with underscores (in the Java), but the names are otherwise identical.

The system automatically checks that the values specified in the key=value string are reasonable, given the variable types.

For example, key.int must be an integer; key.url must specify a legitimate URL; phoneNumber must correctly match a regular expression that describes a legitimate US phone number. Validation is done automatically at load time — you don't have to write any code to check for valid values — and you have complete control over the validation process. For example, you can check that numbers are in range or that a string matches a regular expression. Default values are used automatically if no value is found in the file.

Some of the values in my test file have been constrained, and a ConfigurationError is thrown if a value is incorrect or if a value is out of bounds. For example, intBound must be in the range 1 ≤ value ≤ 3, so if the input file has the line:

    intBound=4

then a ConfigurationError is thrown (on the first attempt to access any of the configuration parameters). An ERROR-level log message that identifies the problem is also output. The same thing happens if the format for the phone number doesn't follow standard US conventions. We'll see how to define constraints in a moment.

All values in the file are typed, and fetched values are always returned as a typed object. (There is a String type, of course.) Seven typed access methods are supported, all of which will throw a ClassCastException if the configuration parameter's actual value is not convertible to the required type:

    Integer i = TestConfiguration. key_int.      intValue();
    Long    l = TestConfiguration. key_long.     longValue();
    Double  d = TestConfiguration. key_double.   doubleValue();
    File    f = TestConfiguration. key_location. fileValue();
    URL     u = TestConfiguration. key_url.      URLValue();
    String  s = TestConfiguration. key_string.   stringValue();
    Boolean b = TestConfiguration. key_boolean.  booleanValue();
    String  n = TestConfiguration. phoneNumber.  stringValue();

All data validation is done for you automatically, and default values are automatically supplied, so you can assume that the returned value is valid. No extra checking is required and no unexpected exceptions are thrown. Also, Autoboxing (automatic conversion to and from basic types) is your friend. The following works fine:

    int     i = TestConfiguration. key_int.    intValue();
    long    l = TestConfiguration. key_long.   longValue();
    double  d = TestConfiguration. key_double. doubleValue();
    boolean b = TestConfiguration. key_boolean.booleanValue();

You will get a ClassCastException if you try something like the following (where the types don't match):

    boolean b = TestConfiguration.key_int.intValue();   // int not convertible to boolean!

The properties defined in the file can be instances of any class that supports a String constructor, but you'll need to use a generic value() method to access those properties with types that aren't directly supported by the configuration system. To get a generic value, try this:

    Object o = TestConfiguration.defaultEverything.value();

You'll have to cast the returned reference to it's actual type, of course.

There's no need to load the properties file explicitly. You just access the element when you need it — aforementioned example code demonstrates literally everything you need to do to access a given property. The file is loaded automatically the first time any of the elements are used, and the loaded values are cached for subsequent use. (The file is read only once.)

However, if any of the verification tests I mentioned earlier fail, the first access of any element throws a ConfigurationError (I presented ConfigurationError in a previous column: It's basically just a lightweight extension of java.lang.Error). If you don't want to bother with exceptions at the point of use, you can preload the file using:

    TestConfiguration.load();

and catch the exception there. Nonetheless, I usually don't bother to catch the exception, because I pretty-much always want the ConfigurationError to terminate the server or webapp. I do want that failure to happen sooner rather than later, so I'll usually put the load() call into main() or my ServletContextListener. The load() process verifies the correctness of every key/value pair in the file, so once you've loaded the file, no other error-detection code is required, anywhere. The usual practice of checking the validity of each property value when you first use that value is typically unnecessary.

So that's it. Pretty painless. We need to do considerable work under the covers to make all this magic possible, of course, but that's where that work belongs — where we don't have to look at it. The basic principle is that you can minimize repetitive work like error checking by moving that work into the class that holds the data.


Related Reading






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video