File:  [Public] / java / classes / org / w3c / rdf / examples / ARPServlet.java
Revision 1.3: download - view: text, annotated - select for diffs
Sat Aug 11 02:11:01 2001 UTC (22 years, 10 months ago) by barstow
Branches: MAIN
CVS tags: HEAD
Bug fixes; replace StringTokenizer with Apache's RE (Regular
Expression) class.

/**
 * ARPServlet - Servlet for the ARP RDF parser
 *
 * Copyright © World Wide Web Consortium, (Massachusetts Institute of
 * Technology, Institut National de Recherche en Informatique et en
 * Automatique, Keio University).
 *
 * All Rights Reserved.
 *
 * Please see the full Copyright clause at
 * <http://www.w3.org/Consortium/Legal/copyright-software.html>
 *
 * This servlet is a wrapper for the ARP RDF parser.  The servlet 
 * expects the following variables through the POST method:
 *
 * RDF - the RDF in an XML syntax 
 *
 * SAVE_DOT_FILE - if "on", the DOT file is saved and a link to the
 *   file is provided to the user
 *
 * SAVE_RDF - if "on", the RDF will be copied to a file.
 *
 * URI - the URI to parse [instead of the RDF]; may not be specified
 *
 * ORIENTATION - the graph's orientation (left to right or top to
 *   bottom
 *
 * FONT_SIZE - the font size to use [10, 12, 14, 16 and 20 are supported]
 *
 * GRAPH_FORMAT - the graph's output format.  Supported values are:
 *
 *     GIF_EMBED - embed the graph as a GIF [the default]
 *     GIF_LINK - don't embed the GIF but create a link for it
 *     SVG_LINK - create the graph in SVG format and create a link to the file
 *     PNG_EMBED - create the graph in PNG format and embed the graph in the 
 *       document that is returned
 *     PNG_LINK - create the graph in PNG format and create a link to the file
 *     PS_LINK - create a PostScript image of the file and a link to the file
 *     HP_PCL_LINK - create a HPGL/2 - PCL (Laserwriter) image of the file 
 *       and a link to the file
 *     HP_GL_LINK - create a HPGL - PCL (pen plotter) image of the file and 
 *       a link to the file
 *     NO_GRAPH - do not generate a graph
 *
 * NTRIPLES if "on" the tabular output will be in the NTriples format;
 *  otherwise a table of Subject, Predicate, Objects will be generated
 *
 * @author Art Barstow <barstow@w3.org>
 *
 * The graphics package is AT&T's GraphVis tool.
 */

package org.w3c.rdf.examples;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.StringTokenizer;
import java.util.Enumeration;
import javax.servlet.*;
import javax.servlet.http.*;

import org.xml.sax.InputSource;
import org.xml.sax.Parser;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.ErrorHandler;
import org.xml.sax.helpers.*;

import org.apache.regexp.RE;

import com.hp.hpl.jena.rdf.arp.*;

public class ARPServlet extends HttpServlet
{
    final static public String	REVISION = "$Id: ARPServlet.java,v 1.3 2001/08/11 02:11:01 barstow Exp $";

    // The email address for bug reports
    private static final String MAIL_TO = "barstow@w3.org";

    // Names of the POST parameters (described above) and their
    // defaults 
    private static final String TEXT            = "RDF";
    private static final String SAVE_DOT_FILE   = "SAVE_DOT_FILE";
    private static final String SAVE_RDF        = "SAVE_RDF";
    private static final String URI             = "URI";
    private static final String NTRIPLES        = "NTRIPLES";
 
    private static final String NODE_COLOR         = "NODE_COLOR";
    private static final String DEFAULT_NODE_COLOR = "black";

    private static final String NODE_TEXT_COLOR         = "NODE_TEXT_COLOR";
    private static final String DEFAULT_NODE_TEXT_COLOR = "black";

    private static final String EDGE_COLOR         = "EDGE_COLOR";
    private static final String DEFAULT_EDGE_COLOR = "black";

    private static final String EDGE_TEXT_COLOR         = "EDGE_TEXT_COLOR";
    private static final String DEFAULT_EDGE_TEXT_COLOR = "black";

    private static final String ORIENTATION         = "ORIENTATION";
    private static final String DEFAULT_ORIENTATION = "TB";  // Top to Bottom

    private static final String FONT_SIZE         = "FONT_SIZE";
    private static final String DEFAULT_FONT_SIZE = "10";

    private static final String FORMAT              = "FORMAT";
    private static final String FORMAT_GIF_EMBED    = "GIF_EMBED";
    private static final String FORMAT_GIF_LINK     = "GIF_LINK";
    private static final String FORMAT_SVG_LINK     = "SVG_LINK";
    private static final String FORMAT_PNG_EMBED    = "PNG_EMBED";
    private static final String FORMAT_PNG_LINK     = "PNG_LINK";
    private static final String FORMAT_PS_LINK      = "PS_LINK";
    private static final String FORMAT_HP_PCL_LINK  = "HP_PCL_LINK";
    private static final String FORMAT_HP_GL_LINK   = "HP_GL_LINK";
    private static final String FORMAT_NO_GRAPH     = "NO_GRAPH";

    // Fonts are not currently configurable
    private static final String DEFAULT_FONT = "arial";

    // Names of the servlet's parameters - for Jigsaw web server
    private static final String SERVLET_TMP_DIR    = "SERVLET_TMP_DIR";
    private static final String GRAPH_VIZ_ROOT     = "GRAPH_VIZ_ROOT";
    private static final String GRAPH_VIZ_PATH     = "GRAPH_VIZ_PATH";
    private static final String GRAPH_VIZ_LIB_DIR  = "GRAPH_VIZ_LIB_DIR";
    private static final String GRAPH_VIZ_FONT_DIR = "GRAPH_VIZ_FONT_DIR";

    // Variables for the servlet's parameters
    private static String m_ServletTmpDir   = null;
    private static String m_GraphVizPath    = null;
    private static String m_GraphVizFontDir = null;
    private static String m_GraphVizLibDir  = null;

    // Names of environment variable need by GraphVis
    private static String DOTFONTPATH     = "DOTFONTPATH";
    private static String LD_LIBRARY_PATH = "LD_LIBRARY_PATH";

    // Names used for temporary files
    private static final String TMP_FILE_PREFIX = "servlet_";
    private static final String SUFFIX_TMP_DIR  = ".tmp";
    private static final String SUFFIX_DOT      = ".dot";
    private static final String SUFFIX_RDF      = ".rdf";

    // Names used for file suffixes and for GraphViz's command line
    // option
    private static final String NAME_GIF      = "gif";
    private static final String NAME_HPGL     = "hpgl";
    private static final String NAME_PCL      = "pcl";
    private static final String NAME_PNG      = "png";
    private static final String NAME_PS       = "ps";
    private static final String NAME_SVG      = "svg";

    // Default GraphViz parameter names and their default values
    // Servlet name
    private static final String SERVLET_NAME = "ARPServlet";

    // Name for the DOT file title
    private static final String DOT_TITLE = "dotfile";

    // The string to use for a namespace name when no
    // namespace is available - e.g. for the RDF that is
    // directly entered into the input form.
    private static final String DEFAULT_NAMESPACE = "genid:";

    /*
     * Create a File object in the m_ServletTmpDir directory
     *
     *@param directory the file's directory
     *@param prefix the file's prefix name (not its directory)
     *@param suffix the file's suffix or extension name
     *@return a File object if a temporary file is created; null otherwise
     */
    private File createTempFile (String directory, String prefix, String suffix) {
        File f;
        try {
            File d = new File(directory);
            f = File.createTempFile(prefix, suffix, d);
        } catch (Exception e) {
            return null;
        }
        return f;
    }

    /*
     * Given a URI string, open it, read its contents into a String
     * and return the String
     *
     *@param uri the URI to open
     *@return the content at the URI or null if any error occurs
     */
    private String getByteStream (String uri) {
        try {
            URL url = new URL(uri);
            InputStream is = url.openStream();
            String s = new String("");

            int c;
            int numRead = 0;

            while ((c = is.read()) != -1) {
                s += (char)c;
                if (numRead == 15) {
                    // A server could return content but not the RDF/XML that
                    // we need.  Check the beginning of s and if it looks like
                    // a generic HTML message, return an error.
                    if (s.startsWith("<!DOCTYPE"))
                        return null;
                }
                numRead++;
            }

            if (s.equals(""))
                // Nothing was returned 
                return null;

            return s;

        } catch (Exception e) {
            return null;
        }
    }

    /*
     * Copy the given string of RDF to a file in the given directory
     *
     *@param dir the file's directory
     *@param rdf the string of RDF
     *@return void
     */

    private void copyRDFStringToFile(String tmpDir, String rdf) 
    {
        try {
            // Generate a unique file name 
            File tmpFile = createTempFile(tmpDir, TMP_FILE_PREFIX, SUFFIX_RDF);
            if (tmpFile == null) {
                // Not really a critical error, just return
                return;
            }

            // Create a PrintWriter for the GraphViz consumer
            FileWriter fw = new FileWriter(tmpFile);
            PrintWriter pw = new PrintWriter(fw);

            pw.println(rdf);
            pw.close();
        } catch (Exception e) {
            // Just return - not critical
            return;
        }
    }

    /*
     * Given the graph's format option, return either the corresponding
     * command line option for that option or the file name suffix for
     * the graph option.  For example GIF files have ".gif" for its
     * suffix and GraphViz uses "-Tgif" for the command line.
     *
     * NOTE: default is GIF.
     *
     *@param graphFormat the graph's output format
     *@param suffix.  If true, the name returned is for the graph's
     * file name suffix; otherwise, the name returned is for the
     * graph's command line option.
     *@return the suffix to use for the graph's output file
     */
    private String getFormatName(String graphFormat, boolean suffix) {

        String name = (suffix) ? "." : "-T";

        if (graphFormat.equals(FORMAT_PNG_EMBED)) return name + NAME_PNG;
        if (graphFormat.equals(FORMAT_PNG_LINK)) return name + NAME_PNG;
        if (graphFormat.equals(FORMAT_SVG_LINK)) return name + NAME_SVG;
        if (graphFormat.equals(FORMAT_PS_LINK)) return name + NAME_PS;
        if (graphFormat.equals(FORMAT_HP_GL_LINK)) return name + NAME_HPGL;
        if (graphFormat.equals(FORMAT_HP_PCL_LINK)) return name + NAME_PCL;
        
        return name + NAME_GIF;
    }

    /*
     * Invokes the GraphVis program to create a graph image from the
     * the given DOT data file
     *
     *@param dotFileName the name of the DOT data file
     *@param outputFileName the name of the output data file 
     *@return true if success; false if any failure occurs
     */
    private boolean generateOutputFile(String dotFileName, String outputFileName, String graphFormat) {
        String environment[] = {DOTFONTPATH     + "=" + m_GraphVizFontDir,
                                LD_LIBRARY_PATH + "=" + m_GraphVizLibDir};

        String formatOption = getFormatName(graphFormat, false);

        String cmdArray[] = {m_GraphVizPath, formatOption, "-o", outputFileName, dotFileName};

        Runtime rt = Runtime.getRuntime();
        try {
            Process p = rt.exec(cmdArray, environment);
            p.waitFor();
        } catch (Exception e) {
            System.err.println("Error: generating OutputFile.");
            return false;
        }

        return true;
    }

    /*
     * Returns a parameter from a request or the parameter's default
     * value.
     *
     *@param req a Servlet request
     *@return if the request contains the specfied parameter its value
     *  in the request is returned; otherwise its default value is
     *  returned
     */
    private String getParameter(HttpServletRequest req, String param, 
        String defString) 
    {
        String s = req.getParameter(param);
        return (s == null) ? defString : s;
    }

    /*
     * If the request contains any graph-related parameters, pass them
     * to the graph consumer for handling
     *
     *@param req the response
     *@param consumer the GraphViz consumer
     */
    private void processGraphParameters (HttpServletRequest req, PrintWriter pw) 
    {
        // Add the graph header
        pw.println( "digraph " + DOT_TITLE + "{ " );

	String s;

        // Look for colors
        String nodeColor     = getParameter(req, NODE_COLOR, DEFAULT_NODE_COLOR);
        String nodeTextColor = getParameter(req, NODE_TEXT_COLOR, DEFAULT_NODE_TEXT_COLOR);
        String edgeColor     = getParameter(req, EDGE_COLOR, DEFAULT_EDGE_COLOR);
        String edgeTextColor = getParameter(req, EDGE_TEXT_COLOR, DEFAULT_EDGE_TEXT_COLOR);
        String fontSize      = getParameter(req, FONT_SIZE, DEFAULT_FONT_SIZE);

        // Orientation must be either 
        String orientation = req.getParameter (ORIENTATION);
        if (orientation.equals("LR"))
            orientation = "LR";
        else
            orientation = DEFAULT_ORIENTATION;

        // Add an attribute for all of the graph's nodes
        pw.println("node [fontname=" + DEFAULT_FONT + 
                      ",fontsize="  + fontSize +
                      ",color="     + nodeColor +
                      ",fontcolor=" + nodeTextColor + "]");

        // Add an attribute for all of the graph's edges
        pw.println("edge [fontname=" + DEFAULT_FONT + 
                      ",fontsize="  + fontSize +
                      ",color="     + edgeColor +
                      ",fontcolor=" + edgeTextColor + "]");

        // Add an attribute for the orientation
        pw.println("rankdir=" + orientation + ";");
    }

    private static class SaxErrorHandler implements org.xml.sax.ErrorHandler
    { 
        ServletOutputStream out;
        boolean silent = false;

        /*
         * Constructuor for a SaxErrorHandler 
         *
	 *@param out the servlet's output stream
	 *@param silent if false, output is suprressed
         */
        public SaxErrorHandler(ServletOutputStream out, boolean silent) 
        {
            this.out = out;
            this.silent = silent;
        }

        /*
         * Create a formatted string from the exception's message
         *
	 *@e the SAX Parse Exception
	 *@return a formatted string
         */
        private static String format(org.xml.sax.SAXParseException e) 
        {
            String msg = e.getMessage();
            if (msg == null)
                msg = e.toString();
            return msg + "[Line = " + e.getLineNumber() + ", Column = " + e.getColumnNumber() + "]";
        }

        /*
         * Handle a parse error
         *
	 *@e the SAX Parse Exception
	 */
        public void error(org.xml.sax.SAXParseException e) 
            throws org.xml.sax.SAXException 
        {
            if (this.silent) return;

            try { 
                this.out.println("Parse Error: " + format(e) + "<br />");
            } catch (IOException ioe) {
                System.err.println("IOException: printing parser error message.");
            }
        }
    
        /*
         * Handle a fatal parse error
         *
	 *@e the SAX Parse Exception
	 */
        public void fatalError(org.xml.sax.SAXParseException e) 
            throws org.xml.sax.SAXException 
        {
            if (this.silent) return;

            try { 
                this.out.println("Fatal Parse Error: " + format(e) + "<br />"); 
            } catch (IOException ioe) {
                System.err.println("IOException: printing parser fatal error message.");
            }
        }
    
        /*
         * Handle a parse warning
         *
	 *@e the SAX Parse Exception
	 */
        public void warning(org.xml.sax.SAXParseException e) 
            throws org.xml.sax.SAXException 
        {
            if (this.silent) return;

            try { 
                this.out.println("Warning: " + format(e) + "<br />"); 
            } catch (IOException ioe) {
                System.err.println("IOException: printing parser warning message.");
            }
        }
    } 

    private static class DotStatementHandler implements StatementHandler 
    {
        PrintWriter pw;
  
        /*
         * Constructuor for a Dot StatementHandler
         *
	 *@param pw the Dot file's PrintWriter
	 */
        public DotStatementHandler(PrintWriter pw)
        {
            this.pw = pw;
        }

        /*
         * Handler for a Resource/Resource/Resource triple (S/P/O)
	 * Outputs the given triple using Dot syntax.
         *
	 *@param subj the subject
	 *@param pred the predicate
	 *@param obj the object (as a Resource)
	 */
        public void statement(AResource subj, AResource pred, AResource obj) 
        {
            if (subj.isAnonymous())
                this.pw.print("\"" + DEFAULT_NAMESPACE + subj.getAnonymousID());
            else
                this.pw.print("\"" + subj.getURI());

            this.pw.print("\" -> ");
            if (obj.isAnonymous())
                this.pw.print("\"" + DEFAULT_NAMESPACE + obj.getAnonymousID());
            else
                this.pw.print("\"" + obj.getURI());

            this.pw.print("\" [label=\"");
            if (pred.isAnonymous())
                this.pw.print(DEFAULT_NAMESPACE + pred.getAnonymousID());
            else
                this.pw.print(pred.getURI());

            this.pw.println("\"];");
        }

        /*
         * Handler for a Resource/Resource/Literal triple (S/P/O)
	 * Outputs the given triple using Dot syntax.
         *
	 *@param subj the subject
	 *@param pred the predicate
	 *@param obj the object (as a Literal)
	 */
        public void statement(AResource subj, AResource pred, ALiteral lit) 
        {
            // Make the box around the Literal
            this.pw.println("node [ shape=\"box\" ];");

            if (subj.isAnonymous())
                this.pw.print("\"" + DEFAULT_NAMESPACE + subj.getAnonymousID());
            else
                this.pw.print("\"" + subj.getURI());

            /*
             * Before outputing the object (Literal) do the following:
             *
             * o Remove leading whitespace
             * o GraphViz/DOT cannot handle embedded line terminators characters
             *   so they must be replaced with spaces
             * o Limit the number of chars to make the graph legible
             * o Embedd the literal in quotes
             * o Escape double quotes
             */
            String s1 = new String(lit.toString());
            s1 = s1.replace('\n', ' ');
            s1 = s1.replace('\f', ' ');
            s1 = s1.replace('\r', ' ');
            if (s1.indexOf('"') != -1)
                s1 = replaceString(s1, "\"", "\\\"");

            // Anything beyond 80 chars makes the graph too large
            String tmpObject;
            if (s1.length() >= 80)
                tmpObject = s1.substring(0, 80) + " ...";
            else
                tmpObject = s1.substring(0, s1.length());

            tmpObject = "\\\"" + tmpObject + "\\\"";

            this.pw.print("\" -> \"" + tmpObject);

            this.pw.print("\" [label=\"");
            if (pred.isAnonymous())
                this.pw.print(DEFAULT_NAMESPACE + pred.getAnonymousID());
            else
                this.pw.print(pred.getURI());
            this.pw.println("\" ];");
        }
    }

    /*
     * Generate a graph of the RDF data model
     *
     *@param out the servlet's output stream
     *@param rdf the RDF text
     *@param req a Servlet request
     *@param source the name of input source
     *@param graphFormat the graph's format
     *@param saveRDF the RDF can be cached [saved to the file system]
     *@param saveDOTFile the DOT file should be cached
     */
    private void generateGraph (ServletOutputStream out, String rdf, 
        HttpServletRequest req, String source, String graphFormat, 
        boolean saveRDF, boolean saveDOTFile) 
    {
        try {
            out.println("<hr title=\"visualisation\">");
            out.println("<h3>Graph of the data model</h3>");

            // Stop if any of the parameters are missing
            if (m_ServletTmpDir == null || 
                m_GraphVizPath == null || 
                m_GraphVizFontDir == null || 
                m_GraphVizLibDir == null) { 
 
                // Put the paths in a comment in the returned content
                out.println("<!-- SERVLET_TMP_DIR = " + m_ServletTmpDir);
                out.println("GRAPH_VIZ_PATH = " + m_GraphVizPath);
                out.println("GRAPH_LIB_DIR = " + m_GraphVizLibDir);
                out.println("GRAPH_FONT_DIR  = " + m_GraphVizFontDir + " -->");

                out.println("Servlet initialization failed.  A graph cannot be generated.");
                return;
            } 

            // The temporary directory
            String tmpDir = m_ServletTmpDir;

            // Must generate a unique file name that the DOT handler
            // will use 
            File dotFile = createTempFile(tmpDir, TMP_FILE_PREFIX, SUFFIX_DOT);
            if (dotFile == null) {
                out.println("Failed to create a temporary DOT file. A graph cannot be generated.");
                return;
            }

            // Create a PrintWriter for the DOT handler
            FileWriter fw = new FileWriter(dotFile);
            PrintWriter pw = new PrintWriter(fw);

            // Create a parser and use the DOT handler to capture
            // the output in a file
	    StringReader sr = new StringReader (rdf);

            ARP parser = new com.hp.hpl.jena.rdf.arp.ARP();

            SaxErrorHandler errorHandler = new SaxErrorHandler(out, true);
            parser.setErrorHandler(errorHandler);

            DotStatementHandler sh = new DotStatementHandler(pw);
            parser.setStatementHandler(sh);

            // Initialize the graph file
            processGraphParameters(req, pw);

            String sError = null;
            try {
                parser.load(sr, source);
            } catch (IOException ioe) {
                sError = "Graph generation IOError: " + source + ": " + ioe.toString();
            } catch (SAXException sax) {
                sError = "Graph generation SAXError: " + source + ": "+ParseException.formatMessage(sax);
            } catch (Exception ex) {
                sError = "Graph generation Exception: " + source + ": " + ex.toString();
            }

            if (sError != null) {
                out.println("An attempt to generate the graph data failed ("
                            + sError + ").");
                pw.close();
                dotFile.delete();
                return;
	    }

            // Add the graph footer
            pw.println( " }");

            // Must close the DOT input file so the GraphViz can
            // open and read it
            pw.close();

            // Generate a unique file name for the output file
            // that will be created
            String suffix = getFormatName(graphFormat, true);
            File outputFile = createTempFile(tmpDir, TMP_FILE_PREFIX, suffix);
            if (outputFile == null) {
                out.println("Failed to create a temporary file for the graph. A graph cannot be generated.");
                dotFile.delete();
                return;
            }

            // Pass the DOT data file to the GraphViz dot program
            // so it can create a graph image of the data model
            String dotFileName = dotFile.getAbsolutePath();
            String outputFileName = outputFile.getAbsolutePath();

            if (!generateOutputFile(dotFileName, outputFileName, graphFormat)) {
                out.println("An attempt to create a graph failed.");
                dotFile.delete();
                outputFile.delete();
                return;
            }

            // Handle the DOT file
            if (saveDOTFile) {
                // Make the DOT file link'able if so requested
                String dotPath = SERVLET_NAME + SUFFIX_TMP_DIR + 
                                 File.separator + dotFile.getName();
                out.println("<a href=\"" + dotPath + "\">Download the DOT file.</a><br /><br />");
            }
            else {
                // Delete it ...
                dotFile.delete();
            }

            // NOTE: Cannot delete the output file here because its
            // pathname is returned to the client
            String imagePath = SERVLET_NAME + SUFFIX_TMP_DIR + File.separator + 
                               outputFile.getName();

            // Handle the embedded image formats first
            if (graphFormat.equals(FORMAT_GIF_EMBED) ||
                graphFormat.equals(FORMAT_PNG_EMBED)) {
                if (outputFile.length() > 0)
                    out.println("<img src=\"" + imagePath + "\"/>");
                else
                    out.println("The graph image file is empty.");
            } else {
                if (outputFile.length() > 0)
                    out.println("<a href=\"" + imagePath + "\">Get/view the graph's image file (" + suffix + ").</a><br /><br />");
                else
                    out.println("The graph image file is empty.");
            }

            // One last thing to do before exiting - copy the RDF to a file
            if (saveRDF)
                copyRDFStringToFile(tmpDir, rdf);

        } catch (Exception e) {
            System.err.println("Exception generating graph: " + e.getMessage());
        }
    }

    /*
     * Search the given string for substring "key"
     * and if it is found, replace it with string "replacement"
     *
     *@param input the input string
     *@param key the string to search for
     *@param replacement the string to replace all occurences of "key"
     *@return if no substitutions are done, input is returned; otherwise
     * a new string is returned.
     */
    public static String replaceString(String input, String key, 
        String replacement) 
    {
        try {
            RE re = new RE(key);
            return re.subst(input, replacement);
        } catch (Exception e) {
            return input;
        }
    }

    /*
     * Print the document's header info
     *
     *@param out the servlet's output stream
     */
    private void printDocumentHeader (ServletOutputStream out) 
    {
        try {

            out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"");
            out.println("      \"http://www.w3.org/TR/REC-html40/loose.dtd\">");
            out.println("<HTML><HEAD>");
            out.println("<TITLE>RDF creation</TITLE>");
            out.println("<LINK HREF=\"rdf.css\" REL=\"stylesheet\">");
            out.println("</HEAD>");
            out.println("<BODY>");

        } catch (Exception e) {
            System.err.println("Exception: " + e.getMessage());
        }
    }

    /*
     * Print the rdf listing
     *
     *@param out the servlet's output stream
     *@param rdf the RDF code
     */
    private void printListing (ServletOutputStream out, String rdf, 
        boolean needCR) 
    {
        try {
            out.println("<hr title=\"original source\">");
            out.println("<h3>The original RDF/XML document</h3>");
            out.println("<pre>");

            String s = replaceString(rdf, "<", "&lt;");
            
            // Now output the RDF one line at a time with line numbers
            int lineNum = 1;
            int nl = 0;
            String terminator = needCR?"\n":"";
            do {
                String tok;
                nl = s.indexOf('\n');
                if ( nl == -1 ) {
                    tok = s;
                } else {
                    tok = s.substring(0,nl);
                    s = s.substring(nl+1);
                }
                out.print( "<a name=\"" + lineNum + "\">" + lineNum +
                               "</a>: " + tok + terminator);
                lineNum++;
            } while ( nl != -1 );

            out.println("</pre>");
        } catch (Exception e) {
            System.err.println("Exception: " + e.getMessage());
        }
    }

    /*
     * Print the header for the triple listing
     *
     *@param out the servlet's output stream
     */
    private void printTripleTableHeader (ServletOutputStream out, boolean nTriples) 
    {
        try {
            if (nTriples) {
                out.println("<h3>Triples of the Data Model in N-Triples Format (Sub, Pred, Obj)</h3>");
                out.println("<pre>");
            } else {
                out.println("<hr title=\"triples\">");
                out.println("<h3>Triples of the Data Model</h3>");
                out.println("<table border><tr>" +
			    "<td><b>Number</b></td>" +
			    "<td><b>Subject</b></td>" +
			    "<td><b>Predicate</b></td>" +
			    "<td><b>Object</b></td>" +
			    "</tr>");
            }
        } catch (Exception e) {
            System.err.println("Exception: " + e.getMessage());
        }
    }

    /*
     * Print the footer info for the triple listing
     *
     *@param out the servlet's output stream
     */
    private void printTripleTableFooter (ServletOutputStream out, 
        boolean nTriples) 
    {
        try {
            if (nTriples)
                out.println("</pre>");
            else
                out.println("</table>");
        } catch (Exception e) {
            System.err.println("Exception: " + e.getMessage());
        }
    }
    
    /*
     * Print the document's footer info
     *
     *@param out the servlet's output stream
     *@param rdf the RDF code
     */
    private void printDocumentFooter (ServletOutputStream out, String rdf) 
    {
        try {

            out.println("<hr title=\"Problem reporting\">");
            out.println("<h3>Feedback</h3>");
            out.println("<p>If you suspect the parser is in error, please enter an explanation below and then press the <b>Submit problem report</b> button, to mail the report (and listing) to <i>" + MAIL_TO + "</i></p>");
            out.println("<form enctype=\"text/plain\" method=\"post\" action=\"mailto:" + MAIL_TO + "\">");
            out.println("<textarea cols=\"60\" rows=\"4\" name=\"report\"></textarea>");
            out.println("<p><input type=\"hidden\" name=\"RDF\" value=\"&lt;?xml version=&quot;1.0&quot;?>");

            // The listing is being passed as a parameter so the '<' 
            // and '"' characters must be replaced with &lt; and &quot, 
            // respectively
            if (rdf != null) {
                String s1 = replaceString(rdf, "<", "&lt;");
                String s2 = replaceString(s1,  "\"", "&quot;");
                out.println(s2 + "\">");
            }

            out.println("<input type=\"submit\" value=\"Submit problem report\">");
            out.println("</form>");
            out.println("<hr/>");
            out.println("</BODY>");
            out.println("</HTML>");

        } catch (Exception e) {
            System.err.println("Exception: " + e.getMessage());
        }
    }

    /*
     * Servlet's get info method
     */
    public String getServletInfo () {
	return "Servlet wrapper for the ARP RDF parser. This is revision " + REVISION;
    }

    /*
     * Servlet's init method
     *
     *@param config the servlet's configuration object
     *@throws ServletException
     */
    public void init(ServletConfig config) throws ServletException 
    {
	super.init (config);

        // Cache the parameters
        m_ServletTmpDir = config.getInitParameter(SERVLET_TMP_DIR);

        // All of the Graph Viz paths extend from GRAPH_VIZ_ROOT
        String GraphVizRoot = config.getInitParameter(GRAPH_VIZ_ROOT);

        m_GraphVizPath = GraphVizRoot + "/" + config.getInitParameter(GRAPH_VIZ_PATH);
        m_GraphVizFontDir = GraphVizRoot + "/" + config.getInitParameter(GRAPH_VIZ_FONT_DIR);
        m_GraphVizLibDir = GraphVizRoot + "/" + config.getInitParameter(GRAPH_VIZ_LIB_DIR);

        if (m_ServletTmpDir == null || GraphVizRoot == null) {
	    System.err.println (
                "<html>" +
                "<h1>Servlet Initialization Error</h1>" +
                "<h2>One or more of the following parameters has not been initialized: " + 
                SERVLET_TMP_DIR + "," + GRAPH_VIZ_ROOT + "," +
                GRAPH_VIZ_FONT_DIR + "," + GRAPH_VIZ_LIB_DIR + "," +
                GRAPH_VIZ_PATH + "." + 
                "</h2>" +
                "</html>");
        }
    }

    /*
     * Servlet's destroy info method
     */
    public void destroy () {
	super.destroy ();
    }

    /*
     * Servlet's doGet info method - NOT supported
     *
     *@param req the request
     *@param res the response
     *@throws ServletException, IOException
     */
    public void doGet (HttpServletRequest req, HttpServletResponse res)
        throws ServletException, IOException 
    {
        String sRDF = req.getParameter(TEXT);
	String sURI = req.getParameter(URI);

        if (sURI == null && sRDF == null) {
	    ServletOutputStream out = res.getOutputStream ();

            out.println("<h1>Neither the RDF or an URI was specified" + 
                        "and one of them must be specified.</h1>");
            return;
        }

        process(req, res, 
                (sRDF != null) ? java.net.URLDecoder.decode(sRDF) : null, 
                (sURI != null) ? java.net.URLDecoder.decode(sURI) : null);
    }

    /*
     * Servlet's doPost method
     *
     *@param req the request
     *@param res the response
     *@throws ServletException, IOException
     */
    public void doPost (HttpServletRequest req, HttpServletResponse res)
        throws ServletException, IOException 
    {
	String sRDF = req.getParameter (TEXT);
	String sURI = req.getParameter (URI);

        if (sURI == null && sRDF == null) {
	    ServletOutputStream out = res.getOutputStream ();

            out.println("<h1>Neither the RDF or an URI was specified" + 
                        "and one of them must be specified.</h1>");
            return;
        }

        process(req,res,sRDF, sURI);
    }

    /*
     * Output a Resource in NTriples syntax
     *
     *@param out the servlet's output stream
     *@param r the Resource to output
     */
    static private void printResource(ServletOutputStream out, AResource r)
    {
        try { 
            if (r.isAnonymous() )
                out.print("_:j" + r.getAnonymousID() + " ");
            else
                out.print("&lt;" + r.getURI() + "&gt; ");
        } catch (IOException ioe) {
            System.err.println("IOException: printing resource.");
        }
    }

    /*
     * Output a Literal in NTriples syntax
     *
     *@param out the servlet's output stream
     *@param l the Literal to output
     */
    static private void printNTripleLiteral(ServletOutputStream out, ALiteral l) 
    {
        try {
            out.print("\"");
            char ar[] = l.toString().toCharArray();

            for (int i=0;i<ar.length;i++) {
                switch (ar[i]) {
                    case '\\':
                        out.print("\\\\");
                        break;
                    case '"':
                        out.print("\\\"");
                        break;
                    case '\n':
                        out.print("\\n");
                        break;
                    case '\r':
                        out.print("\\r");
                        break;
                    case '\t':
                        out.print("\\t");
                        break;
                        default:
                            if ( ar[i] >= 32 && ar[i] <= 255 )
                                out.print(ar[i]);
                }
            }
            out.print("\" ");
        } catch (IOException ioe) {
            System.err.println("IOException: printing literal.");
        }
    }

    /*
     * Control point for outputing an triple in NTriple syntax
     *
     *@param out the servlet's output stream
     *@param subj the subject
     *@param pred the predicate
     *@param objRes the object as a Resource (may be null)
     *@param objLit the object as a Literal (may be null)
     */
    static private void printNTriple(ServletOutputStream out, AResource subj, 
        AResource pred, AResource objRes, ALiteral objLit) 
    {
        try { 
            printResource(out, subj);
            printResource(out, pred);
            if (objRes != null)
                printResource(out, objRes);
            else
                printNTripleLiteral(out, objLit);
            out.println(".");
        } catch (IOException ioe) {
            System.err.println("IOException: printing NTriple.");
        }
    }

    /*
     * Create a HTML anchor from the URI or anonNode of the
     * given Resource
     *
     *@param s the string
     *@return the string as an HTML anchor
     */
    static private String addAnchor(AResource r) 
    {
        if (r.isAnonymous())
            return DEFAULT_NAMESPACE + r.getAnonymousID();
        else
            return "<a href='" + r.getURI() + "'>" + r.getURI() + "</a>";
    }

    /*
     * Output a triple as a row in HTML
     *
     *@param out the servlet's output stream
     *@param subj the subject
     *@param pred the predicate
     *@param objRes the object as a Resource (may be null)
     *@param objLit the object as a Literal (may be null)
     *@param num the statement number
     */
    static private void printTableRow(ServletOutputStream out, AResource subj, 
        AResource pred, AResource objRes, ALiteral objLit, int num) 
    {
        try { 
            out.println("<tr><td>" + num + "</td>");
            out.println("<td>" + addAnchor(subj) + "</td>");
            out.println("<td>" + addAnchor(pred) + "</td>");
            if (objRes != null)
                out.println("<td>" + addAnchor(objRes) + "</td>");
            else {
                out.println("<td>");
                String s1 = objLit.toString().trim();
                s1 = replaceString(s1, "<", "&lt;");
                s1 = replaceString(s1, ">", "&gt;");
                out.println(s1);
                out.println("</td>");
            }
            out.println("</tr>");
        } catch (IOException ioe) {
            System.err.println("IOException: printing TableRow.");
        }
    }

    private static class TripleSH implements StatementHandler 
    {
        ServletOutputStream out;
        boolean isNTriples;
        int numStatements = 0;
  
        /*
         * Constructuor for a Dot StatementHandler
         *
	 *@param out the servlet's output stream
	 *@param isNTriples if true, output using the NTriples
	 *  syntax; otherwise use HTML syntax
	 */
        public TripleSH(ServletOutputStream out, boolean isNTriples)
        {
            this.out = out;
            this.isNTriples = isNTriples;
        }

        /*
         * Handler for a Resource/Resource/Resource triple (S/P/O)
	 * Outputs the given triple using NTriples or HTML syntax.
         *
	 *@param subj the subject
	 *@param pred the predicate
	 *@param obj the object (as a Resource)
	 */
        public void statement(AResource subj, AResource pred, AResource obj) 
        {
            numStatements++;

            if (this.isNTriples)
                printNTriple(out, subj, pred, obj, null);
            else
                printTableRow(out, subj, pred, obj, null, this.numStatements);
        }
        /*
         * Handler for a Resource/Resource/Literal triple (S/P/O)
	 * Outputs the given triple using NTriples or HTML syntax.
         *
	 *@param subj the subject
	 *@param pred the predicate
	 *@param obj the object (as a Literal)
	 */
        public void statement(AResource subj, AResource pred, ALiteral lit) 
        {
            numStatements++;

            if (this.isNTriples)
                printNTriple(out, subj, pred, null, lit);
            else
                printTableRow(out, subj, pred, null, lit, this.numStatements);
        }
    }

    
    /*
     * Handle the servlets doGet or doPut request
     *
     *@param req the servlet's request
     *@param res the servlet's response
     *@throws SevletException, IOException
     */
    private void process(HttpServletRequest req, HttpServletResponse res, 
        String sRDF, String sURI) throws ServletException, IOException 
    {
	ServletOutputStream out = res.getOutputStream ();

	String sSaveRDF     = req.getParameter (SAVE_RDF);
	String sSaveDOTFile = req.getParameter (SAVE_DOT_FILE);
	String sFormat      = req.getParameter (FORMAT);
	String sNTriples    = req.getParameter (NTRIPLES);

        boolean nTriples = (sNTriples != null) ? true : false;

	ARP parser = new com.hp.hpl.jena.rdf.arp.ARP();
        SaxErrorHandler errorHandler = new SaxErrorHandler(out, false);
        TripleSH sh = new TripleSH(out, nTriples);

        String sError = null;

        // Initialize the parser
        parser.setErrorHandler(errorHandler);
        parser.setStatementHandler(sh);

        InputStream in = null;
        String xmlBase = DEFAULT_NAMESPACE;

        printDocumentHeader (out);
        
        if (sURI != null && sURI.length() >= 1) {
            xmlBase = sURI;
            try {
                File ff = new File(sURI);
                in = new FileInputStream(ff);
            }
            catch (Exception ignore) {
                try {
                    URL url;
                    url=new URL(sURI);
                    in = url.openStream();
                    sRDF = getByteStream(sURI);
                    if (sRDF == null)
                        sError = "An attempt to load the RDF from URI '" + sURI + "' failed.  (The file may not exist or the server is down.)";
                } catch (Exception e) {
                    sError = "An attempt to load the RDF from URI '" + sURI + "' failed.  (The file may not exist or the server is down.)";
                }
            }
        }

        if (sError != null) {
            out.println("<h1>" + sError + "</h1>");
            System.err.println(sError);
            printDocumentFooter(out, null);
            return;
        }

        printListing (out, sRDF, sURI != null && sURI.length() >= 1);
        printTripleTableHeader (out, nTriples);

        try {
            if (sURI != null && sURI.length() >= 1) {
                parser.load(in, xmlBase);
            } else {
	        StringReader  sr = new StringReader (sRDF);
                parser.load(sr, xmlBase);
            }
        } catch (IOException ioe) {
            sError = "IOError parsing: " + sURI + ": " + ioe.toString();
        } catch (SAXException sax) {
            sError = "SAXError parsing: " + sURI + ": "+ParseException.formatMessage(sax);
        } catch (Exception ex) {
            sError = "Exception parsing: " + sURI + ": " + ex.toString();
        }

        printTripleTableFooter(out, nTriples);

        if (sError != null) {
	    out.println ("<h1>" + sError + "</h1>\n");
            System.err.println(sError);
            printDocumentFooter(out, null);
            return;
        }

        if (sFormat != null && !sFormat.equals(FORMAT_NO_GRAPH)) {
            generateGraph(out, sRDF, req, xmlBase, sFormat,
                          (sSaveRDF != null) ? true : false,
                          (sSaveDOTFile != null && sSaveDOTFile.equals ("on") ? true : false));
        }

	res.setContentType ("text/html");

        printDocumentFooter(out, sRDF);
    }
}

Webmaster