OSID 2.0 Design Patterns
Implementation Configuration
Tom Coppeto
OnTapSolutions
11 April 2005

An Implementation will generally require access to a configurable set of parameters to avoid the hard coding of customizable data. A good use of a Configuration is to supply the Implementation with parameters such as pathnames set by a systems integrator before runtime. A poor use of the Configuration is to supply the Implementation with runtime parameters through the Interface as a means of shuttling out-of-band information from an Application

I will present an alternate way of managing Configurations that scale well with large numbers of Implementations and keep to the Interface boundary philosophy. First, we need to understand the OsidLoader.

The OsidLoader

The OsidLoader is responsible for finding the Implementation, loading its Configuration, and passing a Context. The Configuration is comprised of two parts: a properties file found in the CLASSPATH, and an additional properties set passed into the OsidLoader from the Application. The loaded properties and additional properties are combined and assigned to the Implementation via the Manager:


    InputStream is = managerClass.getResourceAsStream(managerClassName +
                        ".properties");

    if (null != is) {
        configuration = new java.util.Properties();
        configuration.load(is);
    }

    if (null != additionalConfiguration) {
        java.util.Enumeration enum = additionalConfiguration.propertyNames();

        while (enum.hasMoreElements()) {
            java.io.Serializable key = enum.nextElement();
            if (null != key) {
                java.io.Serializable value = additionalConfiguration.get(key);

                if (null != value) {
                    configuration.put(key, value);
                }
            }
        }
    }

    manager.assignConfiguration(configuration);

Listing 1: Loading of properties inside OsidLoader (modified to illustrate example)

There are several downsides to this. The OsidLOader is responsible for specifying how a Configuration is managed. It expects a separate properties file for each Implementation to be accessible in the CLASSPATH. Although it optimizes for flexibility, for large numbers of implementations this can be tedious. It also provides a means for the Application to shuttle properties into the Implementation and this is a big no-no. But the OsidLoader is defined as a concrete class in this version so we have to work with it the best we can.

The OsidManager

The OsidManager, however, is an interface left to be defined by the implementation. Most implementations of OsidManager I have seen simply stash the Configuration supplied by the OsidLoader and make it accessible to its children.


    public void assignConfiguration (java.util.Properties configuration)
        throws org.osid.OsidException {

        this.configuration = configuration;
        return;
    }

Listing 2: a standard assignConfiguration() method inside OsidManager

Even though we can't mess on the OsidLoader side of the equation, we can do whatever we like on this side. So, lets have some fun.

The first thing to do is close a road to uninteroperability. Now, when assignConfiguration() is invoked, the supplied Configuration is ignored and instead fetches its own.


    public void assignConfiguration(java.util.Properties configuration)
        throws org.osid.OsidException {

        loadConfiguration();
        return;
    }
    
Listing 3: assignConfiguration() method inside OsidManager modified to ignore supplied properties

What does loadConfiguration() do? The answer is hatever you like. The Configuration is specific to an implementation. If there is an implementation that wants to make remote procedure calls to a configuration database, so be it. Although I'll keep things simple and use a java.util.Properties compatible file. With a twist.

Cascading Properties Files

For a collection of Implementations, it is more convenient to have a single properties file and edit the parameters all in one place. At other times it's handy to be able to create a more specific properties file to override the central configuration. Both are useful so let's do both.

The first thing to do look for a general osid.properties configuration file in the CLASSPATH. If that exists then the properties contained inside are loaded.


    protected void loadConfiguration()
        throws org.osid.OsidException {

        this.configuration = new java.util.Properties();

        try {
            String file = ClassLoader.getSystemResource("osid.properties").getPath();
	    if (file != null) {
	        java.io.InputStream is = new java.io.FileInputStream(file + ".properties");
		this.configuration.load(is);
		is.close();
	    }
        } catch (Exception e) {
            // ignore and continue, these are optional
        }

	...

Listing 4: a portion of loadConfiguration() in OsidManager called by assignConfiguration() following the loading of the Implementation

Now we want to load properties for a more specific Implementation, or should that be an Implementation Suite, or perhaps a given OSID? We don't know, so let's search for them all.

For example, for an Implementation with the name of com.ontapsolutions.osids.authentication.kerberos we will search for files in the following order so that in the case of conflicting property names, precedence goes to the more specific rather than the more general properties file.

  1. osid.properties
  2. com.properties
  3. com.ontapsolutions.properties
  4. com.ontapsolutions.osids.properties
  5. com.ontapsolutions.osids.authentication.properties
  6. com.ontapsolutions.osids.authentication.kerberos.properties

It's not reasoanble to make use of all of these files at the same time. This little game of strings provides the integrator with the flexibility to design their property scheme while still allowing instances of local configuration. Here's the code for loadConfiguration().


    protected void loadConfiguration()
        throws org.osid.OsidException {

        String className = getClass().getName();
        java.util.Vector elements = new java.util.Vector();
        int index = 0;
                
        String app = (String) getOsidContext().getContext("context");
        if (app != null) {
            className += "." + app;
        }

        while (index != -1) {
            index = className.indexOf(".", index == 0 ? 0 : index + 1);
            if (index != -1) {
                String token = className.substring(0, index);
                elements.add(token);
            }
        }
        elements.add(className);

        this.configuration = new java.util.Properties();

        try {
            String file = ClassLoader.getSystemResource("osid.properties").getPath();
	    if (file != null) {
                java.io.InputStream is = new java.io.FileInputStream(file + ".properties");
		this.configuration.load(is);
		is.close();
	    }
        } catch (Exception e) {
            // ignore and continue, these are optional
        }

        int size = elements.size();

        for (int i = 0; i < size; i++) {
            try {
                String file = ClassLoader.getSystemResource(elements.elementAt(i) + ".properties").getPath();
                storeConfiguration(file);
            } catch (Exception e) {
                // ignore and continue, these are optional too
            }
        }
        return;
    }


Listing 5: a complete version of loadConfiguration() with cascading properties files

So, you may have noticed the real work was offloaded to another method. The properties cannot simply be loaded as specified in a properties file since they all come together in the same Properties table. They need to be separated by making sure the property name includes full pathname to the class, or does not exceed the scope of its own path. A Properties file cannot introduce properties that effect other implementations outside of it's own tree.

For a properties file of edu.mit.osid.repository.properties:

propertybecomes
dbuseredu.mit.osid.repository.dbuser
edu.mit.osid.repository.ocw.urledu.mit.osid.repository.ocw.url
edu.mit.osid.imageFilenot allowed

Here is a code listing which implements this behavior:


    private void storeConfiguration(String file) {
        java.util.Properties p;

        try {
            java.io.InputStream is = new java.io.FileInputStream(file + ".properties");
            p = new java.util.Properties();
            p.load(is);
            is.close();
        } catch (java.io.IOException ie) {
            return;
        }

        java.util.Enumeration keys = p.propertyNames();
        while (keys.hasMoreElements()) {
            String key    = (String) keys.nextElement();
            String value  = p.getProperty(key);
            String prefix = null;

            int i = key.lastIndexOf('.');
            if (i != -1) {
                prefix = key.substring(0, i);
            }

            if (prefix == null) {

                /*
                 * Unqualified keys get the filename prepended.
                 */

                key  = file + "." + key;                
            } else if ((file.startsWith(prefix) && !file.equals(prefix))) {

                /*
                 * keys cannot exceed the scope of their file
                 */
                
                System.err.println("error in properties file: " + file);
                System.err.println("property " + key + " is out of scope");
                continue;
            } else  if !((file.equals(prefix) || prefix.startsWith(file))) {

                System.err.println("error in properties file: " + file);
                System.err.println("property " + key + " is broken");
                continue;
            }
            this.configuration.setProperty(key, value);
        }
        return;
    }

Listing 6: storeConfiguration(), called by loadConfiguration(), validates and qualifies the property names
Cascading Properties

The next step is providing the means for a manager to access its Configuration. Instead of leaving the Properties object accessible to the manager subclassing this OsidManager, there's more work to do in implementing the cascade.

If, for example, edu.mit.osid.logging.LoggingManager needs to identify a property named log_file, the properties should be searched in the following order giving precedence to more specific properties.:

  1. edu.mit.osidimpl.logging.application.log_file
  2. edu.mit.osidimpl.logging.log_file
  3. edu.mit.osidimpl.log_file
  4. edu.mit.log_file
  5. edu.log_file
  6. log_file

The subclasses of OsidManager acquire their properties through the getConfiguration() method.


    protected String getConfiguration(String key) {

        if (key == null) {
            return (null);
        }

        org.osid.OsidContext context = null;
        String className = getClass().getName();
        java.util.Vector elements = new java.util.Vector();
        String value;
        int index = 0;

        try {
            context = getOsidContext();
        } catch (org.osid.OsidException oe) {
            // skip it
        }
        
        if (context != null) {
            try {
                String app = (String) context.getContext("context");
                if (app != null) {
                    className += "." + app;
                }
            } catch (org.osid.OsidException oe) {
                /* ignore it */
            }
        }
       
        if (this.configuration != null) {
            value = this.configuration.getProperty(className + "." + key);
            if (value != null) {
                return (value);
            }
            
            String path = className;
            while (index != -1) {
                index = path.lastIndexOf(".");
                if (index != -1) {
                    path = path.substring(0, index);
                    value = this.configuration.getProperty(path + "." + key);
                    if (value != null) {
                        return (value);
                    }
                }
            }
        }
        return (null);
    }

Listing 7: getConfiguration() accesses a given property inside the Configuration
Putting It All Together

In any OSID implementation, properties cannot be accessed until after the OssidLoader instantiates the manager and the OsidLoader invokes assignConfiguration(). This leaves the manager to perform a lazy retrieval of properties or to check for a properties initialization flag in each and every method. Or, this can also be made a problem of the OsidManager.


    public void assignConfiguration(java.util.Properties configuration)
        throws org.osid.OsidException {

        loadConfiguration();
        initialize();
        return;
    }

Listing 7: a modified version of assignConfiguration() inside OsidManager with a call to initialize()

Here's an example of what it looks like on the manager side:


    public class AuthenticationManager 
        extends OsidManagerWithCascadingProperties
        implements org.osid.authentication.AuthenticationManager {

        /*
         * properties
         */
    
        private String password_file     = null;
        private int retries              = 3;
    
    
        /**
         *  Constructs a new AuthenticationManager.
         */
    
        public AuthenticationManager() {
            super();
        }
    
    
        /*
         * initialization method
         */
    
        protected void initialize() 
            throws org.osid.OsidException {
            
            String property;
    
            /*
             * initialize properties. See javadoc for detail.
             */
    
            property = getConfiguration("password_file");
            if (property != null) {
                this.password_file = property;
            }
    
            property = getConfiguration("retries");
            if (property != null) {
                try {
                    this.retries = Integer.parseInt(property);
                } catch (NumberFormatException nfe) {
    	        // ignore error, for now
                }
            }
            return; 
        }
    
        ....

Listing 8: an example manager retrieving its Configuration

All of these Property file lookups are performed through ClassLoader.getSystemResource(). This adds a new dimension of flexibility with regard to the locations of these files. The packager should tread wisely.

It's also worth re-emphasizing that properties are specific to a given implementation. Suites of Implementations may be packaged that share some property names in common but there's no requirement that a property name be standardized among Implementations. Such standardization would largely defeat the purpose of Implementation tuning.

This example not only illustrates a different scheme of Configuration management, but also demonstrates that although an Interface inherently restricts what can be passed through it, the job can be done by pushing more of the workload to the implementation.

Code Listings
OsidManagerWithCascadingProperties