The Carbon Java Framework  

The Carbon Core Config Subsystem

Configuration Service Usage

Author: Greg Hinkle (ghinkle at sapient.com)
Version: $Revision: 1.5 $($Author: dvoet $ / $Date: 2003/05/02 14:36:58 $)
Created: January 2002

Introduction

This document describes the Carbon Configuration Service and how it is used to externalize the configurations of a system.

What is configuration

Configuration information is the type of data that is not significant to the implementation of a specific function point; it's value either is not known or does not need to be known until runtime. The best candidates for configuration data are data that:

  • could have many possible values
  • can vary based on how and where the application is deployed
  • is likely to change, but not often
  • needs to change without recompiling and redeploying the application

Centralizing these values simplifies their alteration by eliminating synchronization concerns (i.e. the same value is not stored in 2 different places that can become out of sync). The last consideration for configuration data is that it does not change in response to the normal process of a function point (i.e. does not need or rarely needs alteration while the system is running).

Externalizing configuration

The process of externalizing configuration separates these values from the code where they are used.  This can be done in a number of ways.  One way is to store configuration values in constants. This works well to manage synchronization, but requires recompilation and redeployment of software in order to change values. Another way is to put these values into one or more data files that can be altered without changing code. These values are typically found in properties files in Java and are often cached to improve the speed of lookup. This is a significant improvement on constants, but the data is now kept in non-object form outside of the JVM. This may introduce situations where the configuration data is not valid or does not exist and is abstracted out of the system functionality so that detailed knowledge of the system is needed to alter it. It also does not provide a standard hierarchical structure for this data as constants in packaged classes normally would.

Carbon's configuration model

The Carbon configuration model takes two steps to improve the use of externalized data files for configuration. The first is to provide a simple configuration hierarchy based on the model of folders and documents. The second is to provide more advanced integration between the data and code using something called data binding. This provides much more advanced error checking as well as a consistent method for managing the data and most importantly strongly-typed access to the data.

Carbon Configuration

The primary interface to configuration data is through extensions of the Configuration interface. The configuration service, through data binding, is able to implement any interface that extends the Configuration interface and adheres to the JavaBean™ Specification for properties.

Configuration Interface

The Configuration interface simply provides the basic type for configurations in the system. Extensions of this interface will then be able to directly read data from files through specified interface methods.

Note: According the JVM specification, all methods insides an interface are public.  Declaring them to be public is redundant.  Therefore, the source for Carbon interfaces do not specifically declare the methods as public since this is redundant.

An example configuration interface can be found below. (This is only an example)

Example Configuration Interface

package examples;
public interface WebServiceConfiguration extends Configuration {
    String getServiceName();
    void setServiceName(String value);

    String getServiceAddress();
    void setServiceAddress(String serviceAddress);

    boolean isSecureConnection();
    void setSecureConnection(boolean secureConnection);

    Class TransportClass = org.apache.soap.transport.SOAPHTTPConnection.class;
    Class getTransportClass();
    void setTransportClass(Class value);
}

An example configuration document for the above interface would look like the following:

Example Configuration Data File

<Configuration
    ConfigurationInterface="examples.WebServiceConfiguration">

    <ServiceName>HelloWorld</ServiceName>
    <ServiceAddress>http://ws.examples/HelloWorld</ServiceAddress>
    <SecureConnection>true</SecureConnection>
</Configuration>

These configurations can then be accessed via an instance of the above WebServicesConfiguration that is created by the Carbon Configuration Service. The following example shows the basics of reading information from the above configuration:

Example Use of Configuration

WebServiceConfiguration myConfig = (WebServiceConfiguration)
    Config.getInstance().fetchConfiguration("/WS/example");

if (myConfig.isSecure()) {
    // ... use ssl socket factory ...
} else {
    // ... use standard socket factory ...
}

Transport transport = myConfig.getTransportClass().newInstance();

transport.send(new URL(myConfig.getServiceAddress()), "hello", // .....

You can see the way that the configuration service provides objects implementing the specified configuration interface without any more work than configuring the interface class. In a similar way the set methods may be used to alter the data in that configuration and as needed saved to the underlying store.

Default Values

If a value for an attribute defined by a configuration interface is not present in the XML, null will be returned if the return type is an Object or an InvalidConfigurationException is thrown if the return type is a primitive. If, instead, a default value should be returned, a constant of the same name as the attribute should be defined in the configuration interface as in the TransportClass attribute of the WebServiceConfiguration above. The constant should be the same type as the return type of get method of the attribute.

Property Configuration Interface

Property Configurations provide a weakly-typed (as opposed to the above) mechanism for accessing configuration data similar to property bundles. The interface used to access property configuration is org.sape.carbon.core.config.PropertyConfiguration. Here is an example of a property configuration XML file:

Example PropertyConfiguration
<Configuration
    ConfigurationInterface="org.sape.carbon.core.config.PropertyConfiguration">

    <MyInt>4</MyInt>
    <MyFloat>6.6</MyFloat>
    <MyString>Hello, World!</MyString>
    <MyClass>org.sape.carbon.core.config.format.test.ConfigurationFormatServiceTest</MyClass>

    <Name>
        <First>John</First>
        <Last>Doe</Last>
    </Name>

</Configuration>

The methods of the PropertyConfiguration interface can be used to access properties as specific types. getIntProperty("MyInt") will return an Integer with a value of 4, getProperty("MyInt") will return the String "4". If the property value cannot be formatted into the requested type (e.g. getIntProperty("MyString")), an InvalidConfigurationException is thrown. Properties can be nested as well. To access a nested property use dot notation (e.g. getProperty("Name.First")).

Property Configuration Inheritance

Property configurations can inherit the data values from another as defaults. It works in a similar logical way to the mechanism of class inheritance. The child document may override the values of any parent data, but if the child has no value for a specific instance, the data will be retrieved from the parent.

This type of configuration is useful when creating a set of configurations that closely match, but differ in some logical subset of values. The parent document would then hold the matched values while the children override those values for their specific needs. The following example inherits the values from the document above (assuming it is named /PropertyConfig):

Example PropertyConfiguration Extension
<Configuration
    ConfigurationInterface="org.sape.carbon.core.config.PropertyConfiguration"
    Extends="/PropertyConfig">

    <MyInt>10</MyInt>

</Configuration>

Calling getIntProperty("MyInt") from the above configuration would return an Integer of value 10. Calling getFloatProperty("MyFloat") from the above configuration would return a Float of value 6.6.

Note: At this time, there is no support for inheritance in strongly-typed configurations.

Configuration Document Hierachy

All configurations exist within a tree hierachy similar to a file system. The hierachy can contain folders which can contain other folders or documents. Documents contain configuration information. The naming system is similar to Unix file systems. '/' refers to the root folder, '/foo/bar' refers to a document or folder named bar within the folder foo within the root folder.

Configurations are retrieved by calling the fetchConfiguration method on the class org.sape.carbon.core.config.Config. Config.getInstance().fetchConfiguration("/foo/bar") retrieves the configuration contained in the document /foo/bar. The object returned from this method call can be casted to the specific configuration type that is defined within the document.

Types of Configuration Data

Data stored within configuration documents come in different types depending on the complexity and the cardinality of the information represented.

Simple Types

Simple types require only 1 attribute to store all their information. Examples are Strings, Java primitives and their Object wrappers.

Complex Types

Complex types require more than 1 attribute to store all their information. The main examples of this type are Components. They require a FunctionalInterface and FunctionalImplementation to be specified at a minimum. In most cases, more information is required. In these cases, a sub configuration is used to represent the data.  In the example below, a DataLoader component is required by the Cache service to load data.

Example of Sub-Configuration

<Configuration
    ConfigurationInterface="org.sape.carbon.services.cache.total.TotalCacheConfiguration">

    <FunctionalImplementationClass>com...ReadOnlyCache</FunctionalImplementationClass>
    <FunctionalInterface>org.sape.carbon.services.cache.Cache</FunctionalInterface>

    <DataLoader
        ConfigurationInterface="org.sape.carbon.core.component.ComponentConfiguration">

        <FunctionalInterface>org...TotalCacheDataLoader</FunctionalInterface>
        <FunctionalImplementationClass>org...TestTotalDataLoader</FunctionalImplementationClass>
    </DataLoader>

</Configuration>

Configuration References

Note that the DataLoader element is a complete configuration. In fact, it could reside in its own document outside of Cache configuration document. In this case, the Cache configuration document would then have a reference to the DataLoader Configuration document. In this example, suppose the name of the DataLoader configuration is /cache/DataLoader.

Example of Sub-Configuration Reference

<Configuration
    ConfigurationInterface="org.sape.carbon.services.cache.total.TotalCacheConfiguration">

    <FunctionalImplementationClass>org...ReadOnlyCache</FunctionalImplementationClass>
    <FunctionalInterface>org.sape.carbon.services.cache.Cache</FunctionalInterface>

    <DataLoader>ref:///cache/DataLoader</DataLoader>

</Configuration>

DataLoader Configuration

<Configuration
    ConfigurationInterface="org.sape.carbon.core.component.ComponentConfiguration">

    <FunctionalInterface>org...TotalCacheDataLoader</FunctionalInterface>
    <FunctionalImplementationClass>org...TestTotalDataLoader</FunctionalImplementationClass>

<Configuration>

Accessing Complex Type Configurations

The Configuration system is designed to convert complex types into instances of the types required by the Configuration interface being accessed. If getDataLoader() is called from the CacheConfiguration interface, a DataLoader component is returned, not its underlying configuration. Sometimes, however, it is necessary to access the underlying configuration directly. This can be done by looking up the configuration using the Config class. In this case, the name of the configuration is the name of the parent configuration (e.g. /cache/test/TestCache) followed by the delimiter '/' then followed by the name of the sub-configuration attribute defined in the parents configuration interface (e.g. DataLoader). In this example, the method call would look like: Config.getInstance().fectchConfiguration("/cache/test/TestCache/DataLoader"). This method call will return a configuration object of the type defined in the XML document (in this case, a ComponentConfiguration).

Indexed Types (Arrays)

Attributes of the same type (either simple or complex) can be grouped and accessed as an Array. Individual elements of the array can be accessed or the array can be accessed as a whole. The array order is the order in which the instances appear in the configuration document.

Example Configuration Interface with Indexed Attributes

package examples;
public interface MyConfigurationextends Configuration {
    String[] getValues();
    String getValues(int index);
    void setValues(String[] values);
    void setValues(int index, String value);
    void addValues(String value);
}

Example Configuration Document with Indexed Attributes

<Configuration
    ConfigurationInterface="examples.MyConfiguration">

    <ValuesArray>
        <Values>First Value</Values>
        <Values>Second Value</Values>
        <Values>Third Value</Values>
        <Values>Fourth Value</Values>
    </ValuesArray>
<Configuration>

Note that the addArray(String value) method of MyConfiguration is not part of the JavaBean specification. It is provided to add elements to the end of the array.

Names of Indexed Complex Types

When complex types are indexed, their names change to reflect their index. Their index number surrounded in square brackets ([ ]) is appended to the name.  For example, imagine a configuration named /TestConfig with 1 indexed sub-configuration named SubConfig that has 3 members. Theirs names would be /TestConfig/SubConfig[0], /TestConfig/SubConfig[1], and /TestConfig/SubConfig[2].

Keyed Types (Maps)

Attributes of the same type (either simple or complex) can be grouped and accessed as a Map. Individual elements of the Map can be accessed or the Map can be accessed as a whole. The keys of the map are specified by a Key attribute for each property in the configuration document.

Example Configuration Interface with Map Attributes

package examples;
public interface MyConfigurationextends Configuration {
    Map getValues();
    String getValues(String key);
    void setValues(Map values);
    void setValues(String key, String value);
}

Example Configuration Document with Keyed Attributes

<Configuration
    ConfigurationInterface="examples.MyConfiguration">

    <ValuesMap>
        <Values Key="A">First Value</Values>
        <Values Key="B">Second Value</Values>
        <Values Key="C">Third Value</Values>
        <Values Key="D">Fourth Value</Values>
    </ValuesMap>
<Configuration>

Note that this is a departure from the JavaBean specification as it does not support Maps.

Names of Keyed Complex Types

When complex types are in a Map, their names change to reflect their key. Their key surrounded in square brackets ([ ]) is appended to the name.  For example, imagine a configuration named /TestConfig with 1 keyed sub-configuration named SubConfig that has 3 members with keys A, B, and C. Theirs names would be /TestConfig/SubConfig[A], /TestConfig/SubConfig[B], and /TestConfig/SubConfig[C].

Appendices

Resource URL

JavaBeans Specification

http://java.sun.com/products/javabeans/docs/beans.101.pdf (PDF)
JavaBeans Home http://java.sun.com/products/javabeans/

Copyright © 2001-2003, Sapient Corporation