Reading Models (or other Resources) from the Classpath in Servlets (and Other Applications)

by

In some circumstances, such as deploying models as part of a servlet, it’s convenient to be able to read them from the classpath. This is just one reason why we read models through input streams rather than files. It’s actually really easy:

import com.aliasi.chunk.Chunker;
import com.aliasi.util.Streams;
import java.io.*;

public class ReadModel {

    public static void main(String[] args)
        throws IOException, ClassNotFoundException {

        Chunker chunker = readChunker(args[0]);
        for (int i = 1; i < args.length; ++i)
            System.out.println(chunker.chunk(args[i]));
    }

    static Chunker readChunker(String resourceName) 
        throws IOException, ClassNotFoundException {

        InputStream in = null;
        ObjectInputStream objIn = null;
        try {
            in = ReadModel.class
                 .getResourceAsStream(resourceName);
            objIn = new ObjectInputStream(in);
            return (Chunker) objIn.readObject();
        } finally {
            Streams.closeInputStream(objIn);
            Streams.closeInputStream(in);
        }
        
    }
}

The class can be anything. You can also use this.getClass() if it’s non-static. You might also want to buffer than input for efficiency.

Then I just compile it (making sure LingPipe’s on the classpath):

> javac -cp lingpipe-3.7.0.jar ReadModel.java

then fire it up (making sure LingPipe, the newly compiled class, and the model’s directory are on the class path):

> java \
-cp lingpipe-3.7.0.jar;.;c:/carp/lingpipe/trunk/demos/models/ \
ReadModel \
ne-en-news-muc6.AbstractCharLmRescoringChunker \
"Mr. Smith lives in Washington." \

As is conventional, the backslash (\) indicates that there’s no line break; thus the entire command above is on a single line. it’s critical here that the directory c:\carp\lingpipe\trunk\demos\models\ is on the classpath. This is the directory where we store our models for distribution. The point of putting them on the classpath is that we can now call them by name rather than by an explicit path.

The output is just what you’d expect:

Mr. Smith lives in Washington. : \
[0-9:PERSON@-Infinity, 19-29:LOCATION@-Infinity]

For servlets, just put models in the same place as the other compiled classes or jars in the war (by convention, WEB-INF/lib) and you’re good to go. Here’s the ant target to build the war file for our demos:

  <target name="war" depends="jar">
    <mkdir dir="build/war"/>
    <copy todir="build/war">
      <fileset dir="web"/>
    </copy>

    <mkdir dir="build/war/WEB-INF/lib"/>
    <copy todir="build/war/WEB-INF/lib">
      <fileset file="lingpipe-demos.jar"/> 
      <fileset file="../../lingpipe-4.0.0.jar"/>
      <fileset file="../lib/commons-fileupload-1.2.1.jar"/>
      <fileset file="../lib/commons-io-1.4.jar"/>
      <fileset file="../lib/nekohtml-1.9.11.jar"/>
      <fileset file="../lib/xml-apis-2.9.1.jar"/>
      <fileset file="../lib/xercesImpl-2.9.1.jar"/>
    </copy>

    <jar destfile="build/war/WEB-INF/lib/models.jar">
      <fileset dir="../">
        <include name="models/**"/>
      </fileset>
    </jar>

    <jar destfile="build/${app.name}.war">
      <fileset dir="build/war"/>
    </jar>
  </target>

Then you can build a model in the servlet in the same way as in the standalone example above. Here’s the actual method from our com.aliasi.demo.framework package (in $LINGPIPE/demos/generic, in which the following method is implemented in the abstract base class AbstractTextDemo.readResource(String), which is used by the command-line, GUI, and servlet-based demos:

    protected Object readResource(String resourceName) {
        InputStream in = null;
        BufferedInputStream bufIn = null;
        ObjectInputStream objIn = null;
        try {
            in = this.getClass()
                  .getResourceAsStream(resourceName);
            if (in == null) {
                String msg = "Could not open resource="
                    + resourceName;
                throw new IOException(msg);
            }
            bufIn = new BufferedInputStream(in);
            objIn = new ObjectInputStream(bufIn);
            return objIn.readObject();
        } catch (IOException e) {
            throw new IllegalArgumentException(e.toString());
        } catch (ClassNotFoundException e) {
            throw new IllegalArgumentException(e.toString());
        } finally {
            Streams.closeInputStream(objIn);
            Streams.closeInputStream(bufIn);
            Streams.closeInputStream(in);
        }
    }

If I were doing this today, I’d throw the exceptions rather than converting them to runtime exceptions. And, of course, there’s no reason to throw an IOException then catch it and modify it; the test should just directly throw the right kind of exception.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s