DekGenius.com
Previous Section  < Day Day Up >  Next Section

9.5 The Configuration Service

Let's start by looking at our configuration service, since it is used by all of the other services and interfaces. We need to provide a generic interface for retrieving our configuration options, separating the rest of the application from the details of how those values are stored or retrieved. We are going to use property files via java.util.Properties for the initial implementation.

Here is the class definition for ConfigBean:

package com.relevance.ss.config;

import java.util.Properties;

public class ConfigBean {

    Properties props = new Properties( );
    int maxlinks;
    String[] allowedExtensions;
    String skippedLinksFile;

    public ConfigBean( )
    {
        try
        {
            props.load(getClass( ).getResourceAsStream(
              "/com.relevance.ss.properties"));
            maxlinks = Integer.parseInt(props.getProperty("maxlinks"));
            allowedExtensions= props.getProperty("allowed.extensions").split(",");
            skippedLinksFile = props.getProperty("skipped.links.file");
        }
        catch(Exception ex)
        {
                //log the errors and populate with reasonable defaults, if necessary
        }
    }
        
    public String getSkippedLinksFile( )
    {
        return skippedLinksFile;
    }
    public int getMaxLinks( )
    {
        return maxlinks;
    }
    public String[] getAllowedExtensions( )
    {
        return allowedExtensions;
    }

}

The class provides for the retrieval of three properties by name: MaxLinks, AllowedExtensions, and SkippedLinksFile. MaxLinks determines the maximum size of the searchable domain, AllowedExtensions is the file types the crawler should attempt to index, and the SkippedLinksFile is a logfile for keeping track of all the links skipped by a given indexing event.

Originally, I thought about adding an additional method to allow for future extension of the list of properties:

public String getPropertyByName(String propName)
{
        return props.getProperty(propName);
}

However, adding this method would be confusing and redundant. If the list of properties ever changes, we will have to make changes to the source code for whatever services use the new property; we might as well, then, also update ConfigBean at the same time to expose the new property explicitly. For the sake of simplicity, we'll leave this method out.

Getting the path to the index is not as simple as just reading the path from a file. If we were talking about only a console-based interface to the application, it would be okay. But since we are also going to expose a web service, we have to protect against multiple concurrent uses of the index. Specifically, we need to prevent a user from performing a search on the index while the indexer is updating it.

To ensure this, we implement a simple kind of shadow copy. The configuration file for the index path contains a root path (index.fullpath) and a property for a special extension to the index root path (index.next). index.next has a value at all times of either 0 or 1. Any attempt to use the index for a search should use the current value of index.fullpath + index.next. Any attempt to create a new index should use the alternate value of index.next, write the new index there, and update the value in the property file so future searches will use the new index.

Below is the implementation of IndexPathBean that allows for these behaviors:

package com.relevance.ss.config;

import java.io.IOException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Properties;

public class IndexPathBean {

   private final String propFilePath = "index.properties";
   private String nextIndexPath;
   private String curIndexPath;
   private String nextIndex;
   private Properties props;

    private void getPaths( ) throws IOException
    {
        File f = new File(propFilePath);
        if (!f.exists( )) {
          throw new IOException("properties path " + propFilePath 
                                 + " does not exist");
        }
        props = new Properties( );
        props.load(new FileInputStream(propFilePath));
        String indexRelativePath = props.getProperty("index.next");
        if (indexRelativePath == null) {
          throw new IllegalArgumentException("indexRelativePath not set in " 
                                              + propFilePath);
        }
        nextIndex = Integer.toString(1 - Integer.parseInt(indexRelativePath));
        curIndexPath = props.getProperty("index.fullpath") + indexRelativePath;
        nextIndexPath = props.getProperty("index.fullpath") + nextIndex;
    }

    public String getFlippedIndexPath( ) throws IOException
    {
        getPaths( );
        return nextIndexPath;
    }

    public String getIndexPath( ) throws IOException {
        getPaths( );
        return curIndexPath;
    }
    
        public void flipIndexPath( ) throws IOException
    {   
        getPaths( );
        props.setProperty("index.next", nextIndex);
        props.store(new FileOutputStream(propFilePath), "");
    }

The class exposes three public methods: getters for the current index path and next index path, and a method that flips them. Any class that needs to merely use the index can call getIndexPath( ) to get the current version. Any class that needs to modify the index can call getFlippedIndexPath( ) to get the version that isn't currently in use, and after modifying it, can call flipIndexPath( ) to reset the properties file to the new version. All three public methods rely on a private utility method called getPaths( ), which reads the current values from the property file.

From a simplicity standpoint—and, to a certain extent, transparency as well—we should probably expose the index path methods from ConfigBean, providing a single entry point into the application's configuration settings for the rest of the services. We'll leave the actual functionality separated for ease of maintenance and replacement (in case we have to modify the way the index path is stored over time). To do that, we add the following lines of code to ConfigBean:

IndexPathBean indexPathBean = new IndexPathBean( );
public String getCurIndexPath( )
    {
        String indexPath = "";
        try
        {
            indexPath = indexPathBean.getIndexPath( );
        }
        catch(Exception ex)
        {
        }
        return indexPath;
    }

    public String getNextIndexPath( )
    {
        String indexPath = "";
        try
        {
            indexPath = indexPathBean.getFlippedIndexPath( );
        }
        catch(Exception ex)
        {
        }
        return indexPath;
    }

    public void flipIndexPath( )
    {
        try
        {
            indexPathBean.flipIndexPath( );
        }
        catch(Exception ex)
        {
        }
    }

9.5.1 Principles in Action

  • Keep it simple: use existing Properties tools, not XML

  • Choose the right tools: java.util.Properties

  • Do one thing, and do it well: separate configuration details into separate service, keep simple properties and index path in separate classes

  • Strive for transparency: one entry point for configuration settings, even though there are two implementations

  • Allow for extension: expandable list of allowable link types

    Previous Section  < Day Day Up >  Next Section