<document>
<header>
<product>resin</product>
<resin-2.0>$resin/java_tut/jndi-appconfig.xtp</resin-2.0>
<type>tutorial</type>
<title>Application configuration files using a custom JNDI object</title>

<description>
<p>Applications often need to read, and possibly write,
configuration files.   An excellent 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.  An object of type
<code>AppConfig</code> is obtained with a jndi lookup. It is used to open
files relative to the base directory.</p>
</description>
 <tutorial-startpage>index.jsp</tutorial-startpage>

</header>
<body>


<summary/>

<section title="Files in this tutorial">
<deftable>
<tr><td><viewfile-link file="WEB-INF/web.xml"/>
    <td>Configure the AppConfig object as a resource
<tr><td><viewfile-link file="WEB-INF/classes/example/AppConfig.java"/>
    <td>The AppConfig object provides input and output streams to configuration files
<tr><td><viewfile-link file="WEB-INF/classes/example/TestServlet.java"/>
    <td>A simple example usage of AppConfig that reads and writes a file
<tr><td><viewfile-link file="index.jsp"/>
    <td>The starting page for the tutorial
</deftable>
</section>

<section title="The java code for a custom JNDI object">
<p>A custom JNDI object is implemented similar to a java-bean (see <a
href="doc|config|init">Bean-style initialization</a>).  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".  The
<code>init()</code> method is called by Resin after all of the setters
have been called.</p>

<example file="WEB-INF/classes/example/AppConfig.java"
         re-start="(void setConfig)|(void init)" re-end="}">
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");
  }

  ...
</example>
</section>

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

<p>Configuration of the JNDI object is done with the
<a config-tag="resource"/> 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>

<p>The <a href="doc|config|el">EL configuration</a> variable <var/app.docDir/> is used.</p>

<example title="Configuring the AppConfig JNDI object"
         file="WEB-INF/web.xml"
         re-start="make an example.AppConfig" re-end="</resource>">
  &lt;resource name='config/Application'&gt;
    &lt;type&gt;example.AppConfig&lt;/type&gt;
    &lt;init&gt;
      &lt;config-files-location&gt;${'${'}app.docDir}/WEB-INF/config&lt;/config-files-location&gt;
    &lt;/init&gt;
  &lt;/resource&gt;
</example>

</section>

<section 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 stored in <code>_appConfig</code>.</p>

<example title="Obtaining the AppConfig JNDI object"
         file="WEB-INF/classes/example/TestServlet.java"
         re-start="_appConfig = " re-marker="void init">

  final static String JNDI_NAME = "java:comp/env/config/Application";

  public void init()
    throws ServletException
  {
    try {
      <b>_appConfig = (AppConfig) new InitialContext().lookup(JNDI_NAME);</b>

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

</example>

<p><code>_appConfig</code> is used to open the
configuration files for reading and writing.</p>

<example title="Using the AppConfig JNDI object"
         file="WEB-INF/classes/example/TestServlet.java"
         re-start="_appConfig\." re-marker="use the _appConfig">
...

    InputStream is = _appConfig.openConfigFileRead(inputFile);

...

    OutputStream os = _appConfig.openConfigFileWrite(outputFile);

...
</example>

</section>

<section title="Variation - Hiding the configuration file with getters">

<p>The example in this tutorial is easily modified to allow the hiding of the
configuration file behind <code>get</code> methods of the bean.  Implementing
getters on the confugration bean abstracts the configuration information,
protecting code which uses the configuration information from implementation
details of how the configuration information is read and stored.
</p>

<example title="Hiding the configuration file with getters">
package example;

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

public class AppConfig {
  public final static String APPCONFIG_JNDINAME = "java:comp/env/config/Application";
  private final static String DEFAULT_PROPERTIES = "example/AppConfig.properties";

  private String _configFile;
  private Properties _properties;


  /** 
   * A convenience method that emulates the singleton pattern.
   * It is not a good idea to use static member variables to implement the
   * singleton pattern in an app server because of ClassLoader problems.
   * In this case, JNDI is used to store the object in a safe manner.
   *
   * The AppConfig object has already been instantiated by Resin, the JNDI
   * lookup is used to get a reference to an object that already exists.  
   */
  static public AppConfig getInstance()
    throws NamingException
  {
    AppConfig r = (AppConfig) new InitialContext().lookup(APPCONFIG_JNDINAME);
    if (r == null) 
      throw new NamingException("no object found with jndi name `" + APPCONFIG_JNDINAME + "'");
    return r;
  }

  /**
   * Optionally set the name of a file that provides properties that override
   * the defaults.  The defaults are obtained from a file in the classpath 
   * named 'example/AppConfig.properties'
   *
   * For example, the file containing default properties might be in 
   * WEB-INF/classes/example/AppConfig.properties,
   * or if AppConfig.class is in a jar, the AppConfig.properties 
   * could be in the jar file alongside the AppConfig.class file.
   *
   * AppConfig.properties contains values placed there by the developer.
   * The &lt;config-file&gt; is used to indicate a file that specifies properties
   * that override the defaults, perhaps properties that change depending 
   * on the deployment environment.
   */
  public void setConfigFile(String configFile)
    throws Exception
  {
    _configFile = configFile;
  }

  public void init()
    throws Exception
  {
    InputStream is = null;
    Properties defaults;

    // try to find a default configuration file in the classpath
    ClassLoader loader = Thread.currentThread().getContextClassLoader();
    is = loader.getResourceAsStream(DEFAULT_PROPERTIES);

    if (is != null)
      defaults = new Properties();
      defaults.load(is);
    }
    else {
      // throw an exception here to make the defaults required
      throw new FileNotFoundException(DEFAULT_PROPERTIES);
    }

    if (_configFile == null) {
      // just use the defaults
      _properties = defaults;
    } 
    else {
      // the properties in _configFile override the defaults
      is = new FileInputStream(_configFile);
      _properties = new Properties(defaults);
      _properties.load(is); 
    }
  }

  public String getFoo()
  { 
    return _properties.getProperty("foo");
  }

  public String getBar()
  { 
    return _properties.getProperty("bar");
  }
}
</example>
<example>
&lt;web-app&gt;
 &lt;resource name='config/Application'&gt;
    &lt;type&gt;example.AppConfig&lt;/type&gt;
 &lt;/resource&gt;
&lt;/web-app&gt;

or

&lt;web-app&gt;
 &lt;resource name='config/Application'&gt;
    &lt;type&gt;example.AppConfig&lt;/type&gt;
    &lt;init&gt;
      &lt;config-file&gt;${'${'}app.docDir}/WEB-INF/AppConfig-override.properties&lt;/config-file&gt;
    &lt;/init&gt;
 &lt;/resource&gt;
&lt;/web-app&gt;
</example>
<example>
package example;

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

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

public class TestServlet extends HttpServlet {
  /**
   * _appConfig is stored locally, for efficiency.
   */
  AppConfig _appConfig;

  public void init()
    throws ServletException
  {
    try {
      _appConfig = AppConfig.getInstance();
    } catch (NamingException e) {
      throw new ServletException(e);
    }
  }

  ...

  String foo = _appConfig.getFoo();
  String bar = _appConfig.getBar();

  ...

}
</example>

</section>

<section title="Availability of AppConfig from different web-apps">

<p>
The availability of AppConfig to different web-apps depends upon the context
that the &lt;resource ...&gt; configuration is placed within.
</p>

<p>
If the configuration is placed as a child of &lt;web-app&gt;, then that
instance of AppConfig is available only to that web-app.
</p>

<example>
&lt;web-app&gt;
 &lt;resource name='config/Application'&gt;
    &lt;type&gt;example.AppConfig&lt;/type&gt;
 &lt;/resource&gt;
&lt;/web-app&gt;
</example>

<p>
If it is placed as a child of &lt;host&gt;, that instance of AppConfig is
available to all web-app's within the host.
</p>

<example>
&lt;host&gt;
  ...
 &lt;resource name='config/Application'&gt;
    &lt;type&gt;example.AppConfig&lt;/type&gt;
 &lt;/resource&gt;
  ...
&lt;/host&gt;
</example>

<p>
If it is placed as a child of &lt;server&gt;, that instance of AppConfig is
available to all web-app's within all host's within that server.
</p>

<example>
&lt;server&gt;
  ...
 &lt;resource name='config/Application'&gt;
    &lt;type&gt;example.AppConfig&lt;/type&gt;
 &lt;/resource&gt;
  ...
&lt;/server&gt;
</example>

<p>
In the case of &lt;server&gt; or &lt;host&gt;, the example.AppConfig class
needs to be available in the classpath.  The easiest way to accomplish that is
to place a jar with that class in $RESIN_HOME/lib, or you can use an explicit 
<a config-tag="class-loader"/>.
</p>

</section>

<section title="Compatibility">

<p>
Although the <a config-tag="resource"/> tag is Resin specific the pattern shown
in this tutorial is good for portability.  If your application needs to work
in another app-server, you can use a startup facility such as a <a
config-tag="load-on-startup"/> servlet to manually configure the bean, call the
init() method, and stuff the instance into JNDI.  
</p>

</section>

</body>
</document>
