JingDAO Tutorial: Context Configuration

In this example we'll look at how to add configuration information to your DAO's. Each DaoManager has a context which is essentially a map of key and value pairs. Your DAO's can access the context information via one of three methods: Avalon Context Lifecycle , JavaBean methods , Constructor Parameter . The following examples show three implementations of a ContextDao , each using a different configuration (or IoC) type.

package org.jadetower.dao.test.daos;

import java.io.File;

public interface ContextDao {

    public String getName();
    public File getFile();

}
     

You can use the Avalon Contextualize lifecycle method to access context configuration values. To do this, implement org.apache.avalon.framework.context.Contextualizable interface and access the values as follows:

package org.jadetower.dao.test.daos.impl;

import org.jadetower.dao.test.daos.ContextDao;
import java.io.File;
import java.util.Map;
import org.apache.avalon.framework.context.Context;
import org.apache.avalon.framework.context.Contextualizable;
import org.apache.avalon.framework.context.ContextException;

public class ContextDaoAvalonImpl implements ContextDao, Contextualizable{

  String m_name = null;
  File m_file = null;

  public ContextDaoAvalonImpl(){
  }

  public void contextualize(Context context) throws ContextException {
    m_name = (String) context.get("name");
    m_file = (File) context.get("file");
  }

  public String getName() {
    return m_name;
  }

  public File getFile() {
    return m_file;
  }


}
     

To use the JavaBean setter method, make sure that all configurable fields have getters and setters and ensure that the context contains keys which match your field names.

package org.jadetower.dao.test.daos.impl;

import org.jadetower.dao.test.daos.ContextDao;
import java.io.File;

public class ContextDaoBeanImpl implements ContextDao {

  protected String m_name = null;
  protected File m_file = null;

  public ContextDaoBeanImpl(){
  };

  public String getName() {
    return m_name;
  }

  public File getFile() {
    return m_file;
  }

  public void setName(String name){
    m_name = name;
  }

  public void setFile(File file){
    m_file = file;
  }

}
     

To use the Constructor Parameter method, include a java.util.Map parameter in your DAO constructor. This map will contain the Context keys and values.

package org.jadetower.dao.test.daos.impl;

import org.jadetower.dao.test.daos.ContextDao;
import java.io.File;
import java.util.Map;

public class ContextDaoMapImpl implements ContextDao {

  Map m_context = null;

  public ContextDaoMapImpl(Map context){
    m_context = context;
  }

  public String getName() {
    return (String) m_context.get("name");
  }

  public File getFile() {
    return (File) m_context.get("file");
  }
}
     

Context Configuration

Now that we know how to access this context configuration information, let's look at how to set it. Again, we have several options: programmatic configuration , XML configuation , and BeanShell scripts .

In the first example, we can pass in a Map to the DaoContainer to intialize the context values. The following code section comes from the ParentContextTestCase unit test:

  public void testProgrammaticContext() throws Exception {
    String[] configuration = new String[] {
        "src/conf/parent-context-example.xconf"};

    HashMap context = new HashMap();
    context.put("name", expectedName);
    context.put("file", new File("src/conf/parent-context-example.xconf"));

    DaoContainer daoContainer = new DaoContainer(configuration, context, null);

    DaoManager manager = daoContainer.getManager("programmatic");
    ContextDao dao = (ContextDao) manager.getDao("context");
    String name = dao.getName();
    String file = dao.getFile().getName();
    boolean pass = name.equalsIgnoreCase(expectedName) &&
        file.equalsIgnoreCase(expectedFile);
    assertTrue(pass);
  }
     

In this code snippet, we can see that we can create a map and pass it as a constructor parameter to the DaoContainer. Context maps passed in this way are referred to as the parent context. We still need some configuration information that tells the DaoManager what to pass from the parent context to the DAOs:

<?xml version="1.0" encoding="ISO-8859-1"?>
<container>
    <manager id="programmatic">
      <context>
        <entry name="name" uri="parent://name"/>
        <entry name="file" uri="parent://file"/>
      </context>
      <daos>
         <dao name="context" uri="class://org.jadetower.dao.test.daos.impl.ContextDaoMapImpl" />
      </daos>
    </manager>
</container>
     

Notice the parent:// URI scheme. This tells the DaoManager to retrieve a value from the parent context with the key specified by the URI path. The value is then placed in the DAO context using the ID attribute value.

To use the other configuration methods, we don't need to do anything special during container setup. Everything is handled in the configuration file where you can directly specify context entries:

    <manager id="bean">
      <context>
         <entry name="name" value="org.jadetower.dao.test.daos.ContextDao" />
         <entry name="fileName" value="src/conf/context-example.xconf"/>
         <entry name="file" uri="class://java.io.File">
            <parameter type="java.lang.String" value="${fileName}"/>
         </entry>
      </context>
      <daos>
         <dao name="context" uri="class://org.jadetower.dao.test.daos.impl.ContextDaoBeanImpl" />
      </daos>
    </manager>
      

In this configuration we specify three context values: name , fileName , and file . The DAOs we illustrated above will only access name and file . An <entry> element is used to define these values. It has the form:

<entry name="KEY" value="VALUE" uri="scheme://path"/>

The uri attribute is optional. It defaults to class://java.lang.String in which case it specifies that the value is a String data type. Another valid entry could look like this:

<entry name="number" value="42" uri="java.lang.Integer"/>

Any valid Resolver URI scheme can be used in the uri attribute. For more information on URI Resolvers, see the Resolvers section of the tutorial.

Another interesting feature of context configuration is that previously defined entries can be reused to define new entries. This helps for objects which have complicated constructors. For example, to create the java.io.File object that we use in the context, we reuse the fileName entry as a constructor paramter. In this way you can create rather complex context objects.

However, if you're using many complicated objects in your context and you don't want to hard code that information in your application, it may be much easier to use BeanShell to script your context values. The following is a simple BeanShell script that creates a context equivalent to those we've already seen:

import java.util.HashMap;
import java.io.File;

HashMap context = new HashMap();
context.put("name","org.jadetower.dao.test.daos.ContextDao");

File file = new File("src/conf/context-example.xconf");
context.put("file",file);

return context;
      

The only requirement of such scripts is that the script returns a java.util.Map object with the context values assigned. There are two ways to include BeanShell scripts in your configuration. You can either specify a script file or embed the script directly in the XML:

    <manager id="scripted" extends="bean">
      <context>
          <script file="src/conf/context.bsh"/>
      </context>
    </manager>

    <manager id="embedded" extends="bean">
      <context>
         <script>
import java.util.HashMap;
import java.io.File;

HashMap context = new HashMap();
context.put("name","org.jadetower.dao.test.daos.ContextDao");

File file = new File("src/conf/context-example.xconf");
context.put("file",file);

return context;
         </script>
      </context>
      

One final note about this configuration. Notice the extends attribute for the manager. This tells the DaoContainer to reuse the configuration of another named manager (in this case the "bean" manager we looked at above) and allow the new configuration to override any of those values. So in this case, we reuse the <doas/> section of the "bean" DaoManager. This extension feature is currenlty only available when you use the supplied DaoContainer and thus cannot be used when a DaoManager is embedded in an Avalon container.