You are viewing a plain text version of this content. The canonical link for it is here.
Posted to general@incubator.apache.org by Bruce Conrad <ba...@mindspring.com> on 2003/07/12 08:50:41 UTC

Project Proposal: Configuration Utility

I have developed a very useful Configuration utility that provides very 
simple, flexible and powerful functionality for managing one or more 
configurations in a java environment.

It allows for a combination of a hierarchy of configuration files, 
substitution variables and property variables. Methods are provided to get 
values stored in a configuration dictionary with a variety of types 
(String, array, integer, float, boolean, etc) and default values.

I've used it in several applications and I've had very good feedback from 
other developers. It solves some very basic problems common to any medium 
to large size application. I'd like to contribute it to the open source 
community and see it enhanced. Any suggestions on how to proceed with this 
would be welcome. (I hope this is the right place to post this. If not, my 
apologies).

A more complete description follows:

Configuration Object


This Configuration package provides very simple, flexible and powerful 
functionality for managing one or more configurations in a java environment.


THE PROBLEM

Most projects require configurations for multiple versions of that project. 
You may have multiple developers connecting to separate database instances, 
each with their own password. Additionally, multiple production, stage, and 
training platforms may also need to be supported. Files may reside in one 
directory on Windows, another directory on Unix.

At the same time, many configuration parameters are unchanged across 
multiple environments.

Many projects use one or more property files to define configuration 
parameters. There are many problems inherent in this approach. If you want 
to have different values depending on your environment you have several 
choices, none of them good.
1.      Modify your configuration files every time you deploy in a new 
environment.
This is error prone and time consuming, especially if you are building often.
2.      Maintain separate copies of configuration files in different 
locations with the same file name.
This means you can’t have your configuration files in your source control 
repository since they have the same name. It also means that when you 
change one, you need to change every version of that file in every 
location. That is a more difficult task since the files may be in different 
locations and are not tracked by version control.

3.      Maintain separate copies but with different names.
You’ve solved the version control problem, but you still need to change 
every version when you change one.

When clients install applications remotely, the client is often required to 
make modifications to the configuration file. Every time they get a new 
version of the application, they need to refit their local updates. This 
too is error prone and time consuming.

Often, configuration parameters are stored in property files. But property 
files are java specific. If the same configuration parameters are needed in 
a non-java environment, or even scripts used to control a java environment, 
even more configuration files may be necessary.


THE SOLUTION

What is needed is a hierarchical approach to configuration files. The 
ninety percent of configuration values that do not change can be maintained 
in a base file. The other ten percent (or less) may be maintained in their 
own distinct configuration file. At run time, the files are layered on top 
of each other to provide a flexible, manageable configuration. For example, 
in a development environment myhost.config.xml combines with dev.config.xml 
and base.config.xml to form my unique configuration.

Each configuration file may then be maintained in version control as they 
have unique names. Only the base files need to be modified when base values 
change, and it is easy to see the difference between versions. Another 
major benefit is that changes to the base configuration file will be 
exhaustively tested before deployment.

Often part of a value may change, but not the entire value. Many values may 
follow this pattern. For instance, in application X, development files are 
written to …/dev/files. Support emails go to devsupport@xcompany.com, with 
a subject line of “dev error”.  In production, files are written to 
…/prod/files. Support emails go to prodsupport@xcompany.com, with a subject 
line of “prod error in application X.” The only thing that has really 
changed is the words “dev” and “prod” There is really only one change here, 
not three. The use of substitution variables enables us to accomplish this 
with only one entry.

In the case of applications installed by clients, if the client were to 
make their modifications to a client configuration that inherited from the 
base configuration, it would eliminate the need to refit the client 
modifications when the product is upgraded. It would also be easier to see 
exactly what changes the client made, making it easier to debug 
configuration problems.

This configuration object provides many of these solutions. It allows for a 
combination of a hierarchy of configuration files, substitution variables 
and property variables. Methods are provided to get values stored in a 
configuration dictionary with a variety of types (String, array, integer, 
float, boolean, etc) and default values.

After some initial setup, the end result will be a cleaner, more easily 
maintainable application.



USAGE

The class Config implements the public interface for the configuration 
object. To get a singleton instance of this object, use 
Config.getInstance(). To get values from the config object, use 
Config.getInstance().getValue(sectionName, key) Or Config. 
getInstance().getValue(sectionName, key, defaultValue) If you use a default 
value and the section/key is not found, the default value will return. If 
you don't use a default value and the section/key is not found, 
ConfigException is thrown.

To set Config values, use Config.setConfigurationValue(sectionName, key, 
newValue);
For the most part, however, config values are set during the initial 
parsing of one or more ini files.

To show a complete listing of Config values, use 
Config.getInstance().printConfigurationDictionary()


  SETTING UP THE CONFIGURATION FILE(S):

There are two formats for specifying configurations. One is XML based, the 
other is standard INI file format.


XML Format

<configuration>

     <section name="locos">
         <entry key="instance" value="development" />
     </section>

    <section name="Paths">
       <entry key="locosHome" value="d:/[locos{instance}/project/" />
       <entry key="locosExternal" value="d:/external/[locos{instance}/" />
    </section>

    <section name="attachments">
       <entry key="attachmentDirectory" 
value="[paths{locosExternal}attachments/"/>
    </section>

</configuration>
INI File Format

  Section names are in brackets, followed by key/value pairs.

  Following is a sample file:

  [project]
  instance=development
  timeout=5

  [Paths]
  locosHome=d:/project/[locos{instance}/
  locosExternal=d:/[locos{instance}/

  [attachments]
  attachmentDirectory=[paths{locosHome}attachments/


  The first section is very straightforward. A call to
  getValue("project", "instance") returns value "development".


SUBSTITUTION WITHIN ONE CONFIGURATION FILE

The second value, section Paths, key locosHome, uses the value of  project, 
instance to build its value. So, locosHome resolves to 
d:/project/development . The next entry, attachmentDirectory, uses the 
previous entry to build its value. Thus, attachmentDirectory resolves to 
d:/project/development/attachment.

This comes in very handy when there are many values that have common 
relative paths or some other value in common. The root value can be changed 
once without having to change every value.


SUBSTITUTION VARIABLES

As shown in the above sample file you can "build" values using substition 
variables.  Substitution variables are in the format: [section{key}

PROPERTY VARIABLES

You may also substitute property variables in the format: $xxx$ . You can 
use this to dynamically build values such as classpaths or file paths based 
on the settings of your environment.


MULTIPLE CONFIGURATION FILES

One of the most useful aspects of this configuration object is the ability 
to layer multiple configuration files. This can be very helpful as you can 
maintain one stable base configuration file and make modifications to other 
config files. The “include” directive instructs the Configuration parser to 
find the included configuration file. Files are parsed in reverse order. 
Thus, if a prod config file includes a base config file, the base config is 
parsed first. The prod config will be parsed next and any duplicate 
section/key values will overlay those in the base config file.
Note: The ini format has not been upgraded to recognize the include directive.

You can maintain a base configuration file for an application while 
allowing users to modify a local or site copy. That way, the base 
configuration file is unchanged and it is easy to see what values are 
overridden. If you upgrade the base configuration file, local overrides do 
not need to be re-implemented. Developers could take advantage of this by 
making their changes to a test configuration file, leaving the base 
unchanged and making it very obvious as to what is being overridden.


COMMENTS AND CONTINUATION LINES

Other syntax rules are "*" for comments and "," for continuation lines 
(This pertains to ini file format only).


HELPER METHODS

The most used method, getValue, returns its value as a String. 
getIntegerValue, getFloatValue, getLongValue, and getBooleanValue will 
return values in the corresponding primitive types.

Additional helper methods are getArrayValue which parses values separated 
by commas, semicolons, or spaces into a String array.

getValuesStartingWith returns a List of values starting with the specified 
Key/Value.

See the javadoc for more information.


CASE INSENSITIVITY

This configuration package is case insensitive. While values retain their 
case, the keys to access them will work regardless of case. So, if, the 
section is "aSection" and  the key is aKey", the value may be retrieved 
using AsEcTiOn, AkEy or any other case pattern. There is a slight 
performance benefit if you do use the correct case.


STARTUP PARAMETERS

Two parameters tell the Configuration package what configuration file to 
look for and where to find it. These are:
·       config.filename
o       Specifies the name of the starting configuration file to use.
Default value is <hostname>.config. xml.
- where hostname is the name of the current machine. Please note that the 
hostname is converted to lower case. This has no effect on Windows but it 
could on another OS.

·       config.location
o       The two valid values are “classpath” and “file”. The default value 
is classpath.
config..location=classpath tells Configuration to look in the classpath for 
the configuration file.

config..location=file tells Configuration to look in the file system for 
the configuration file.

Startup Examples:
1.      Process Config file prod.config.xml in the file system.

·       java Dconfig.file=c:/project/xyz/prod.config.xml 
Dconfig.location=file com.domain.StartServer





2.      Process Config  file prod.config.xml in the classpath. Note that 
these two are the same as classpath is the defaulf config.location.

·       java Dconfig.file=prod.config.xml 
Dconfig.location=classpath  com.domain.StartServer
·       java Dconfig.file=prod.config.xml com.domain.StartServer


3.      Process Config  file hostname.config.xml in the classpath, assuming 
we are running on host named prodserver. Note that these three are the same 
as classpath is the defaulf config.location and prodserver..config.xml is 
the default config.file on host prodserver



·       java Dconfig.file=prodserver..config.xml Dconfig.location=classpath 
com.domain.StartServer
·       java Dconfig.file=prodserver.config.xml com.domain.StartServer
·       java com.domain.StartServer



INITIALIZATION

The first time the Configuration object is invoked it lazily initializes 
the configuration files and builds the configuration dictionary. Subsequent 
calls retrieve values from the dictionary. It would be wise to make a call 
in your startup for better control. Simply add the line:
Config.getInstance()
to invoke initialization.


REPROCESSING and CONFIGURATION LISTENERS

To reprocess Configuration on a running system, call 
Config.getInstance().reprocessConfig().

If you have made changes to your configuration files, these changes will be 
reflected in the current running configuration. You may want to use a jsp 
to invoke this call. Examples are available.

Classes implement the ConfigListener interface to detect when configuration 
changes have been made via reprocessConfig(). For example, your code may 
have Scheduler object that initializes its schedule parameters using 
Configuration. However, if you wish to have the flexibility to change these 
parameters, you may have your Scheduler implement ConfigListener. Then when 
you change the parameters in your configuration file and run 
reprocessConfig(), the Scheduler will know to reprocess its schedule 
parameters.

In another case, you may have a Thread that makes a call to Configuration 
to determine how long to sleep. If it calls once at startup and caches that 
value, it will need to implement ConfigListener to know if the value has 
changed. If it calls Configuration on each iteration, there would be no 
need to implement ConfigListener as it will get the changed value on the 
subsequent invocation.

PERL IMPLEMENTATION

There is a perl implementation of this configuration functionality. This 
allows us to write perl scripts to start, stop, or otherwise control a java 
environment using many of the same configuration values.