<s1 title="Application configuration files using a custom JNDI object">

<summarylist/>

<p>A common requirement is the need to read, and possibly write,
configuration files for an application.   An excellant way to
accomplish this is to implement a custom JNDI object, which is
easily configured and easily obtained from anywhere in the
application.</p>

<p>This implementation of the concept allows you to configure a base
directory for configuration files, and use the object obtained with a
jndi lookup to open files relative to the base directory.</p>

<s2 title="The java code for a custom JNDI object">
<p>A custom JNDI object is implemented similar to a java-bean.  Setter
methods like <code>setFoo(String foo)</code> are used to set values
that are specified in the configuration.</p>

<p>In this case, a single setter is provided that matches the
configuration parameter "config-files-location"</p>

<example>
package example;

import java.net.*;
import java.io.*;

import com.caucho.vfs.*;

public class AppConfig {
  ConfigFilesLocation _cfl = null;

  /**
   * Set the base for subsequent call's to openConfigFileRead()
   * and openConfigFileWrite()
   *
   * @param location a file path or url
   */
  public void setConfigFilesLocation(String location)
    throws Exception
  {
    _cfl = new ConfigFilesLocation();
    _cfl.setLocation(location);
  }

  public void init()
    throws Exception
  {
    if (_cfl == null)
      throw new Exception("'config-files-location' must be set");
  }

  /**
   * Create and return a ReadStream for a configuration file, with
   * the file being relative to the base previously set with
   * setConfigFilesLocation()
   *
   * @return a WriteStream, which can be treated as a
   * java.io.InputStream if desired
   *
   * @see java.io.InputStream
   */
  public ReadStream openConfigFileRead(String file)
    throws IOException
  {
    return _cfl.openRead(file);
  }

  /**
   * Create and return an WriteStream for a configuration file, with
   * the file being relative to the base previously set with
   * setConfigFilesLocation().
   *
   * @return a WriteStream, which can be treated as a
   * java.io.OutputStream if desired
   *
   * @see java.io.OutputStream
   */
  public WriteStream openConfigFileWrite(String file)
    throws IOException
  {
    return _cfl.openWrite(file);
  }

  public static class ConfigFilesLocation {
    Path _path;  // com.caucho.vfs.Path

    public void setLocation(String location) 
    {
      _path = Vfs.lookup(location);
    }

    public ReadStream openRead(String file)
      throws IOException
    {
      Path p = _path.lookup(file);
      return p.openRead();
    }

    public WriteStream openWrite(String file)
      throws IOException
    {
      Path p = _path.lookup(file);
      return p.openWrite();
    }
  }
}
</example>
</s2>

<s2 title="Configuring the custom JNDI object">

<p>Configuration of the JNDI object is done with the
<var/resource-ref/> tag.</p>

<p>The example here configures the location of the configuration files
as <code>WEB-INF/config</code> (which means you need to make
sure the directory exists for the example to work).  It is good to
hide the files somewhere under <code>WEB-INF</code>, because a browser
will not be able to read the files, just the application.</p>

<example>
&lt;web-app&gt;
  &lt;!-- make an example.AppConfig object, available with the
     - JNDI name "java:comp/env/config/Application"
    --&gt;
  &lt;resource-ref res-ref-name='config/Application'
                class-name='example.AppConfig'&gt;
    &lt;!-- config-files-location
       -
       - set's the base for subsequent config file lookups.
       -
       - EL variables are very useful here, such as 
       - app.appDir, server.rootDir, host.rootDir
       - See http://www.caucho.com/resin-3.0/config/el.xtp 
       -
       - You can also use an http url, although you will not be able
       - to write files then.
      --&gt;
    &lt;init-param config-files-location='${'${'}app.docDir}/WEB-INF/config'/&gt;
    &lt;!-- &lt;init-param config-files-location='http://gryffindor/config'/&gt; --&gt;
  &lt;/resource-ref&gt;

  ...

&lt;/web-app&gt;
</example>
</s2>

<s2 title="Obtaining and using the object">

<p>An instance of the object is retrieved in the application using
JNDI.  Since the configuration will not change, it is best to store the
results of the lookup.</p>

<p>In this example servlet, an instance of the object is retrieved in
the init() method and then used in the doGet() to open the
configuration files for reading and writing.</p>

<example>
package example;


import javax.servlet.*;
import javax.servlet.http.*;

import java.io.*;
import java.util.*;
import javax.naming.*;

public class TestAppConfig extends HttpServlet {
  AppConfig _appConfig;
  final static String _jndi_name = "java:comp/env/config/Application";

  public void init()
    throws ServletException
  {
    try {
      _appConfig = (AppConfig) new InitialContext().lookup(_jndi_name);

      if (_appConfig == null)
        throw new ServletException("`" + _jndi_name + "' is an unknown JNDI resource");
    } catch (NamingException e) {
      throw new ServletException(e);
    }
  }

  public void doGet(HttpServletRequest req,
                    HttpServletResponse res)
    throws IOException, ServletException
  {
    res.setContentType("text/html");
    PrintWriter out = res.getWriter();

    String inputFile = req.getParameter("inputFile");
    String outputFile = req.getParameter("outputFile");

    out.println("&lt;html>&lt;head>&lt;title>TestAppConfig&lt;/title>&lt;/head>&lt;body>");

    if (inputFile != null && inputFile.length() > 0) {
      // demonstration of reading a configuration file
      out.println("&lt;h2>inputFile: " + inputFile + "&lt;/h2>");
      try {
        // copy the input file to the output
        InputStream is = _appConfig.openConfigFileRead(inputFile);
        final int bufsz = 1024;
        char[] buf = new char[bufsz];
        int l;

        Reader in = new BufferedReader(new InputStreamReader(is));

        out.println("&lt;pre>");
        while ((l = in.read(buf,0,bufsz)) &gt; 0) {
          out.write(buf,0,l);
        }
        out.println("&lt;/pre>");
      } catch (Exception ex) {
        throw new ServletException(ex);
      }
    } else if (outputFile != null && outputFile.length() > 0) {
      // demonstration of writing a configuration file
      out.println("&lt;h2>outputFile: " + outputFile + "&lt;/h2>");
      try {
        OutputStream os = _appConfig.openConfigFileWrite(outputFile);
        PrintWriter outfile = new PrintWriter(os);
        Date now = new Date();
        outfile.print("Configuration file made from 'TestAppConfig' ");
        outfile.println(now);
        outfile.close();
      } catch (Exception ex) {
        throw new ServletException(ex);
      }
      String url = res.encodeURL(req.getRequestURI() + "?inputFile=" + outputFile);
      out.println("The configuration file has been written.");
      out.println("You can now &lt;a href='" + url + "'>view it as an inputFile&lt;/a>");
    } else {
      outputFile = "test.txt";
      String url = res.encodeURL(req.getRequestURI() + "?outputFile=" + outputFile);
      out.println("Try &lt;a href='" + url + "'>writing to a configuration file&lt;/a>");
    }
  }
}
</example>

<p>To enable the servlet, you can add the following to
WEB-INF/web.xml, and the url will be (for example)
<code>http://localhsot:8080/myapp/test</code></p>

<example>
  &lt;servlet&gt;
    &lt;servlet-name&gt;test&lt;/servlet-name&gt;
    &lt;servlet-class&gt;example.TestAppConfig&lt;/servlet-class&gt;
  &lt;/servlet&gt;

  &lt;servlet-mapping&gt;
    &lt;url-pattern&gt;/test&lt;/url-pattern&gt;
    &lt;servlet-name&gt;test&lt;/servlet-name&gt;
  &lt;/servlet-mapping&gt;
</example>


</s2>
</s1>
