File:  [Public] / java / classes / org / w3c / rdf / examples / ARPServlet.java
Revision 1.59: download - view: text, annotated - select for diffs
Wed Aug 14 00:57:08 2002 UTC (21 years, 10 months ago) by duerst
Branches: MAIN
CVS tags: HEAD
one more bug fix

/***********************************************************************
 *
 * ARPServlet - this servlet implements an RDF Validation service.  As
 *  of this writing, the following RDF validation service used this
 *  servlet:
 *
 *   http://www.w3.org/RDF/Validator/
 *
 ***********************************************************************
 *
 * 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.  See the following
 * for information about the ARP RDF parser:
 *
 *  http://www.hpl.hp.co.uk/people/jjc/arp/
 *
 ***********************************************************************
 *
 * Implementation notes:
 *
 * o This servlet supports the HTTP POST operation; it does not 
 *  support the HTTP GET operation
 *
 * o Depending upon the parameters given to the servlet it may 
 *  invoke a GraphViz suprocess to generate a graph of the RDF.  
 *  See the following for more information about GraphViz:
 *
 *   http://www.research.att.com/sw/tools/graphviz/
 *
 *  The servlet assumes version 1.7.4 of GraphViz.
 *
 * o Depending upon the parameters given to the servlet, the RDF
 *  to be validated may be copied to a file.  The name of the file
 *  is automatically generated via Java's temporary file APIs.  The
 *  location of the directory where the file is stored is configured
 *  via the serverlet's init() method.  See below for more information.
 *
 * o See the section on Server Initialization for more information.
 *
 ***********************************************************************
 *
 * HTTP POST parameters - the servlet expects/assumes the following 
 *  variables are defined via the HTTP POST request:
 *
 * RDF - the RDF (assumed to be in RDF/XML syntax) to be validated
 *
 * SAVE_DOT_FILE - if "on", the GraphViz DOT file is saved and a 
 *   link to the file is returned; otherwise the DOT file is not saved
 *
 * SAVE_RDF - if "on", the RDF will be copied to a file; otherwise
 *   the RDF is not copied to a file
 *
 * EMBEDDED_RDF - if "on", then the RDF is not enclosed in <RDF>...</RDF>
 *   tags; otherwise it assumed that the RDF is enclosed in these tags.
 *
 * URI - the URI of the RDF to validate
 *
 * PARSE - if "Parse RDF", then parse RDF from the textarea;
 *   if "Parse URI: " then parse the RDF at the URI.
 *
 * ORIENTATION - the graph's orientation (left to right or top to
 *   bottom); default is left to right
 *
 * FONT_SIZE - the font size to use (10, 12, 14, 16 and 20 are 
 *   supported); the default is 10
 *
 * ANON_NODES_EMPTY - if "on", anonymous nodes are not labeled; otherwise
 *   anonymous nodes are labeled;
 *
 * TRIPLES_AND_GRAPH - support values are:
 *
 *     PRINT_BOTH - display triples and a graph (the default)
 *     PRINT_TRIPLES - only display the triples
 *     PRINT_GRAPH - only display the graph
 *
 * 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
 *
 * NTRIPLES if "on" the tabular output will be in the NTriples format;
 *  otherwise a table of Subject, Predicate, Objects will be generated
 *
 ***********************************************************************
 *
 * Server Initialization - this servlet requires the following 
 *  parameters be set in the servlet's init() method - via the
 *  ServletConfig object:
 *
 * GRAPH_VIZ_ROOT - the absolute path of the top-level directory containing
 *   GraphViz's binary distribution
 *
 * GRAPH_VIZ_PATH - the relative path (based on GRAPH_VIZ_ROOT) of
 *   the DOT executable (e.g. dotneato/dot) - the program used to generate
 *   a graph from a DOT file.
 *
 * GRAPH_VIZ_FONT_DIR - the relative path (based on GRAPH_VIZ_ROOT) of
 *   the fonts directory used by GraphViz (e.g. Fonts)
 *
 * SERVLET_TMP_DIR - the absolute path of the directory to be used to
 *   store temporary files used by the servlet and GraphViz.  This 
 *   directory must be writable by the servlet.  
 *
 *   NOTE - Some files created by the servlet are not removed by 
 *     servlet (e.g. graph image files).
 *
 * If any of these parameters are not defined, the servlet will NOT 
 * validate the RDF.
 *
 ***********************************************************************
 *
 * Dependencies - this servlet requires the following Java packages
 *   as well as GraphViz (described above):
 *
 * ARP RDF parser: http://www.hpl.hp.co.uk/people/jjc/arp/download.html
 *
 * SAX-based XML parser: e.g. Xerces at http://xml.apache.org/
 *
 * Java servlet package: http://java.sun.com/products/servlet/archive.html
 *
 * Apache Regular Expression: http://jakarta.apache.org/builds/jakarta-regexp/release/v1.2/
 *
 ***********************************************************************
 *
 * Author: Art Barstow <barstow@w3.org>
 * Author (internationalization): Martin J. Duerst <duerst@w3.org>
 *
 * $Id: ARPServlet.java,v 1.59 2002/08/14 00:57:08 duerst Exp $
 *
 ***********************************************************************/

// http://dev.w3.org/cvsweb/java/classes/org/w3c/rdf/examples/
package org.w3c.rdf.examples; 

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.StringTokenizer;
import java.util.Enumeration;
import java.util.Hashtable;

// http://java.sun.com/products/servlet/2.2/javadoc/javax/servlet/package-summary.html
import javax.servlet.*;	 
import javax.servlet.http.*;
import javax.mail.internet.ContentType;

// http://xml.apache.org/apiDocs/org/xml/sax/package-summary.html
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.*;

// http://jakarta.apache.org/regexp/apidocs/org/apache/regexp/RE.html
import org.apache.regexp.RE;
import org.apache.regexp.RESyntaxException;

// http://www.hpl.hp.co.uk/people/jjc/arp/apidocs/index.html
import com.hp.hpl.jena.rdf.arp.*; 

public class ARPServlet extends HttpServlet
{
    final static public String	REVISION = "$Id: ARPServlet.java,v 1.59 2002/08/14 00:57:08 duerst Exp $";

    // The email address for bug reports
    private static final String MAIL_TO = "www-rdf-validator@w3.org";

    // Names of the POST parameters (described above) and their
    // defaults (if applicable)
    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 EMBEDDED_RDF      = "EMBEDDED_RDF";
    private static final String URI               = "URI";
    private static final String PARSE             = "PARSE";
    private static final String NTRIPLES          = "NTRIPLES";
    private static final String ANON_NODES_EMPTY  = "ANON_NODES_EMPTY";
 
    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";

    // Print graph and/or triples
    private static final String TRIPLES_AND_GRAPH = "TRIPLES_AND_GRAPH";
    private static final String PRINT_BOTH        = "PRINT_BOTH";
    private static final String PRINT_TRIPLES     = "PRINT_TRIPLES";
    private static final String PRINT_GRAPH       = "PRINT_GRAPH";

    // Graph formats
    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";

    // 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_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;

    // Names of environment variable needed 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 to prefix anonymous nodes.
    private static final String ANON_NODE = "genid:";

    // 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 = "online:";

    // exception used by getRDFfromURI
    private class getRDFException extends Exception {
        public getRDFException (String s) {
	    super (s);
        }
    }

    /*
     * Create a File object from the given directory and file names
     *
     *@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 getRDFfromURI (String uri) throws getRDFException
    {
	/* add something like this code here, to allow reading from a file:
	   (if we really want to allow this!)
	   File ff = new File(uri);
	   in = new FileInputStream(ff);
	*/
	URL url = null;
	try {
	    url = new URL(uri);
	} catch (MalformedURLException e) {
	    throw new getRDFException("Malformed URI.");
	}

        URLConnection con = null;
	try {
	    con = url.openConnection();
	    con.setRequestProperty("Accept", "application/rdf+xml");
	    con.connect();
	} catch (Exception e) {
	    throw new getRDFException("Unable to open connection.");
	}
	String contentT = con.getContentType();
	String HTTPcharset = null;
	if (contentT != null) {
	    ContentType contentType = null;
	    try {
		contentType = new ContentType(con.getContentType());
	    } catch (javax.mail.internet.ParseException e) {
		throw new getRDFException("Unparsable content type.");
	    }
	    HTTPcharset = contentType.getParameter("charset");
	}
	
	// need buffer for lookahead for encoding detection
	BufferedInputStream bis = null;
	try {
	    bis = new BufferedInputStream(con.getInputStream());
	} catch (IOException e) {
	    throw new getRDFException("Cannot open stream.");
	}
	bis.mark(200); // mark start so that we can get back to it
	String s = "";
	
	try {  // read start of file as bytes
	    int c;
	    int numRead = 0;
	    while ((c = bis.read()) != -1) {
		s += (char)c;
		if (numRead++ >= 195) break;
	    }
	} catch (IOException e) {
	    throw new getRDFException("IOException while starting reading.");
	}
	
	if (s.equals(""))
	    // Nothing was returned 
	    throw new getRDFException("Empty document, ignored.");
	
	// 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"))
	    throw new getRDFException("Document looks like HTML, ignored.");

	String APPFcharset = null;  // 'charset' according to XML APP. F
	int ignoreBytes = 0;
	if (s.startsWith("\u00FE\u00FF")) {
	    APPFcharset = "UTF-16BE";
	    ignoreBytes = 2;
	}
	else if (s.startsWith("\u00FF\u00FE")) {
	    APPFcharset = "UTF-16LE";
	    ignoreBytes = 2;
	}
	else if (s.startsWith("\u00EF\u00BB\u00BF")) {
	    APPFcharset = "UTF-8";
	    ignoreBytes = 3;
	}
	else if (s.startsWith("\u0000<\u0000?")) {
	    APPFcharset = "UTF-16BE";
	}
	else if (s.startsWith("<\u0000?\u0000")) {
	    APPFcharset = "UTF-16LE";
	}
	else if (s.startsWith("<?xml")) {
	    APPFcharset = "US-ASCII";
	}
	else if (s.startsWith("\u004C\u006F\u00A7\u0094")) {
	    APPFcharset = "CP037";  // EBCDIC
	}

	// convert start of xml input according to APPFcharset
	String xmlstart = null;
	try {
	    xmlstart = new String(s.substring(ignoreBytes).getBytes("iso-8859-1"), APPFcharset);
	} catch (UnsupportedEncodingException e) {
	    throw new getRDFException("Unsupported encoding '"+APPFcharset+"'.");
	}
	RE r;
	try {
	    r = new RE("<\\?xml[ \\t\\n\\r]+version[ \\t\\n\\r]?=[ \\t\\n\\r]?(['\"])([a-zA-Z0-9_:]|\\.|-)+\\1[ \\t\\n\\r]+encoding[ \\t\\n\\r]?=[ \\t\\n\\r]?(['\"])([A-Za-z]([A-Za-z0-9._]|-)*)\\3");
	} catch (RESyntaxException res) {
	    throw new getRDFException("Wrong regular expression syntax.");
	}
	// r.setMatchFlags(MATCH_NORMAL | MATCH_SINGLELINE); 
	String XMLcharset = null;
	if (r.match(xmlstart) && r.getParenStart(0)==0)
	    XMLcharset = r.getParen(4);
	if (HTTPcharset != null)
	    HTTPcharset = HTTPcharset.toUpperCase(); 
	if (XMLcharset != null)
	    XMLcharset = XMLcharset.toUpperCase(); 

	String finalCharset = null;
	if (HTTPcharset != null) { 
	    if (XMLcharset != null && !HTTPcharset.equals(XMLcharset)) 
		throw new getRDFException("Charset conflict: Content-Type: "
                    + contentT+ ". XML encoding: " +  XMLcharset + ".");
	    finalCharset = HTTPcharset; 
	} 
	else if (XMLcharset != null) 
	    finalCharset = XMLcharset;
	if ((finalCharset != null && finalCharset.equals("UTF-16")) ||
	        (finalCharset == null && APPFcharset.startsWith("UTF-16")))
	    if (ignoreBytes == 2)
		finalCharset = APPFcharset;  // use correct endianness
	    else
		throw new getRDFException("Illegal XML: UTF-16 without BOM.");
	if (finalCharset == null)
	    finalCharset = "UTF-8";

	try {
	    bis.reset();                 // move back to start of stream 
	    bis.skip(ignoreBytes);       // skip BOM 
	} catch (IOException e) {
	    throw new getRDFException("IOException while resetting stream.");
	}

	InputStreamReader isr = null; 
	try {
	    isr = new InputStreamReader(bis, finalCharset); 
	} catch (UnsupportedEncodingException e) {
	    throw new getRDFException("Unsupported encoding '"+finalCharset+"'.");
	}

	StringBuffer sb = new StringBuffer("");
        int charnum = 0;
	try {  // read whole file as characters
	    int c;
	    while ((c = isr.read()) != -1) {
		sb.append((char)c);
		charnum++;
	    }
	} catch (IOException e) {
	    throw new getRDFException("IOException while reading URI at character "
                + charnum + " using encoding " + XMLcharset + ".");
	}

	// todo: fix encoding parameter in xml pseudo-PI 

	return sb.toString();
    }

    /*
     * Copy the given string of RDF to a file in the given directory.
     * This is only done if the servlet is explictly asked to save
     * the RDF to a file.
     *
     *@param tmpDir the file's directory
     *@param rdf the string of RDF
     */
    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) {
            System.err.println(SERVLET_NAME + ": error occured trying to save RDF to file '" + tmpDir + TMP_FILE_PREFIX + SUFFIX_RDF + "'.");
            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 
     *@param graphFormat the graph's format
     *@return true if success; false if any failure occurs
     */
    private boolean generateGraphFile(String dotFileName, 
	String outputFileName, String graphFormat) 
    {
        String environment[] = {DOTFONTPATH + "=" + m_GraphVizFontDir};

        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
     *@param param the name of the parameter
     *@param defString the string returned if the param is not found
     *@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 pw the PrintWriter
     *@param consumer the GraphViz consumer
     */
    private void processGraphParameters (HttpServletRequest req, PrintWriter pw)
    {
	// Print the graph header
        pw.println("digraph " + DOT_TITLE + "{ " );

        // 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
    { 
        PrintWriter out;
        boolean silent = false;
	String fatalErrors = "";
	String errors = "";
	String warnings = "";

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

        /*
         * Create a formatted string from the exception's message
         *
	 *@param 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
         *
	 *@param e the SAX Parse Exception
	 */
        public void error(org.xml.sax.SAXParseException e) 
            throws org.xml.sax.SAXException 
        {
            if (this.silent) return;

	    this.errors += "Error: " + format(e) + "<br />";
        }
    
        /*
         * Handle a fatal parse error
         *
	 *@param e the SAX Parse Exception
	 */
        public void fatalError(org.xml.sax.SAXParseException e) 
            throws org.xml.sax.SAXException 
        {
            if (this.silent) return;

	    this.fatalErrors += "FatalError: " + format(e) + "<br />";
        }
    
        /*
         * Handle a parse warning
         *
	 *@param e the SAX Parse Exception
	 */
        public void warning(org.xml.sax.SAXParseException e) 
            throws org.xml.sax.SAXException 
        {
            if (this.silent) return;

	    this.warnings += "Warning: " + format(e) + "<br />";
        }

        /*
         * Return the error messages
         *
	 *@return the error messages or an empty string if there are
	 * no messages
	 */
	public String getErrors()
	{
	   return this.errors;
	}

        /*
         * Return the fatal error messages
         *
	 *@return the fatal error messages or an empty string if there are
	 * no messages
	 */
	public String getFatalErrors()
	{
	   return this.fatalErrors;
	}

        /*
         * Return the warning messages
         *
	 *@return the warning messages or an empty string if there are
	 * no messages
	 */
	public String getWarnings()
	{
	   return this.warnings;
	}
    } 

    /*
     * Generate a graph of the RDF data model
     *
     *@param out the servlet's output stream
     *@param pw the graph file's PrintWriter
     *@param dotFile the File handle for the graph file
     *@param rdf the RDF text
     *@param req a Servlet request
     *@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(PrintWriter out, PrintWriter pw,
	File dotFile, String rdf, HttpServletRequest req, String graphFormat, 
        boolean saveRDF, boolean saveDOTFile) 
    {
        try {
            out.println("<hr title=\"visualisation\">");
            out.println("<h3>Graph of the data model</h3>");

            // The temporary directory
            String tmpDir = m_ServletTmpDir;

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

            // 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 (!generateGraphFile(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 (RESyntaxException e) {
            return input;
        }
    }

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

            out.println( "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"" +
                "      \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">" +
                "<html><head>\n" +
                "<title>RDF Validator</title>\n" +
                "<link href='http://www.w3.org/StyleSheets/base.css' " +
                "rel='stylesheet' type='text/css'/>\n" +
                "<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/>\n" +
		"<style type='text/css'>\n" +
		"  TD {" +
		"    background:#EEEEEE;" +
		"    font-family:'courier new',courier,serif;" +
		"  }" +
		"</style>\n" +
                "</head>\n" +
                "<body>\n");

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

    /*
     * Print the rdf listing
     *
     *@param out the servlet's output stream
     *@param rdf the RDF code
     *@param needCR if true, add a CarriageReturn to the output; if false,
     * do not add it
     */
    private void printListing (PrintWriter out, String rdf, 
        boolean needCR) 
    {
        try {
            out.println("<hr title=\"original source\">" +
                        "<h3>The original RDF/XML document</h3>" +
                        "<pre>");

            String s = replaceString(rdf, "&", "&amp;");
            s = replaceString(s, "<", "&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 (printListing): " + e.getMessage());
        }
    }

    /*
     * Print the header for the triple listing
     *
     *@param out the servlet's output stream
     *@param nTriples if true, output is N-Triples syntax
     */
    private void printTripleTableHeader (PrintWriter out, boolean nTriples) 
    {
        try {
            if (nTriples) {
                out.println("<h3>Triples of the Data Model in " +
                   "<a href=\"http://www.w3.org/2001/sw/RDFCore/ntriples/\">" +
                   "N-Triples</a> Format (Sub, Pred, Obj)</h3>" +
                   "<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 (printTripleTableHeader): " + e.getMessage());
        }
    }

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

            String s;

            s = "<hr title=\"Problem reporting\">" +
                "<h3>Feedback</h3>" +
                "<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>" +
                "<form enctype='text/plain' method='post' action='mailto:" + MAIL_TO + "'>" +
                "<textarea cols='60' rows='4' name='report'></textarea>";
            out.println(s);

            out.println("<input type='hidden' name='RDF' value=\"&lt;?xml version=&quot;1.0&quot;&gt;");

            // 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;
                s1 = replaceString(rdf, "<",  "&lt;");
                s1 = replaceString(s1,  ">",  "&gt;");
                s1 = replaceString(s1,  "\"", "&quot;");
                out.println(s1);
            }
            out.println("\"\\>");

            out.println("<input type='submit' value='Submit problem report'\\>" +
                        "</form></body></html>");

        } catch (Exception e) {
            System.err.println("Exception (printDocumentFooter): " + 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);
System.out.println("GRAPH_VIZ_ROOT   = " + GraphVizRoot);
System.out.println("GRAPH_VIZ_PATH   = " + m_GraphVizPath);
System.out.println("GRAPH_VIZ_FNTDIR = " + m_GraphVizFontDir);
System.out.println("SERVLET_TMP_DIR  = " + m_ServletTmpDir);

        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_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) {
            res.setContentType ("text/html;charset=utf-8");
	    PrintWriter out = res.getWriter ();

            out.println("<h1>Data Error</h1>" +
                "Must specify the RDF (RDF is a string) or the URI parameter." +
                "</h1>");
            return;
        }

	try {
            process(req, res, 
                    (sRDF != null) ? java.net.URLDecoder.decode(sRDF) : null, 
                    (sURI != null) ? java.net.URLDecoder.decode(sURI) : null);
	} catch (Exception e) {
            System.err.println("Exception: URLDecoder.decode()");
	}
    }

    /*
     * Servlet's doPost method
     *
     *@param req the request
     *@param res the response
     *@throws ServletException, IOException, java.io.UnsupportedEncodingException
     */
    public void doPost (HttpServletRequest req, HttpServletResponse res)
        throws ServletException, IOException 
    {
	// String encoding = req.getCharacterEncoding();
        // if (encoding == null) {
	//    req.setCharacterEncoding("UTF-8");
	// }
	String sRDF = new String(req.getParameter(TEXT).getBytes("iso-8859-1"), "utf-8");
	String sURI = new String(req.getParameter(URI).getBytes("iso-8859-1"), "utf-8");
	String parse = req.getParameter(PARSE);
	boolean parseRDF = true;
	if (parse != null)
            parseRDF = parse.equals("Parse RDF");
        else if (sURI != null)  // keep working even if PARSE is not present
            parseRDF = false;            
        
        if ((!parseRDF && sURI == null) || (parseRDF && sRDF == null)) {
            res.setContentType ("text/html;charset=utf-8");
	    PrintWriter out = res.getWriter ();

            out.println("<h1>" + (parseRDF ? "RDF" : "URI") + " was not specified.</h1>");
            return;
        }
	if (parseRDF)
	    sURI = null;
	else
	    sRDF = null;

        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(PrintWriter out, AResource r)
    {
        if (r.isAnonymous() )
            out.print("_:j" + r.getAnonymousID() + " ");
        else
           out.print("&lt;" + r.getURI() + "&gt; ");
    }

    /*
     * Convert to Hex and padd left with zeroes
     *
     *@param in the integer to convert and padd
     *@param in the length of the result
     *@return the padded string
     */
     // MJD: is there an easier way to do this?
    static private String hexPadd (int number, int length)
    {
	String t = Integer.toHexString(number).toUpperCase();
	int hexlength = t.length();

        if ( hexlength > length ) {    // too long, truncate
	    hexlength = length;
	}

	int zerolength = length - hexlength;
	String r = "";

	for (int i=0; i < zerolength; i++) {
	    r += "0";
	}
	for (int i=0; i < hexlength; i++) {
	    r += t.charAt(i);
	}
	return r;
    }

    /*
     * Output a Literal in NTriples syntax
     *
     *@param out the servlet's output stream
     *@param l the Literal to output
     */
    static private void printNTripleLiteral(PrintWriter out, ALiteral l) 
    {
        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] <= 127 )
                        out.print(ar[i]);
                    else if ( ar[i] < 0xD800 || ar[i] >= 0xE000)
			out.print("\\u" + hexPadd(ar[i], 4) );
	            else  {  // deal with surrogates
			     // check for correct surrogate pair
                             // this code should probably move somewhere else:
                             // check when we get the input
                        if ( ar[i] >= 0xDC00 ) {
			    out.print("{{{error: lone low surrogate}}}");
			}
		        else if ( ++i >= ar.length ) {
                            out.print("{{{error: lone surrogate at end of string}}}");
			}
			else if ( ar[i] < 0xDC00 || ar[i] >= 0xE000 ) {
			    out.print("{{{error: high surrogate not followed by low surrogate}}}");
			}
			// no errors, actually print
			else {
			    int scalarvalue = 0x10000 + (ar[i-1] * 1024) + ar[i];
			    out.print("\\U" + hexPadd(scalarvalue, 8) );
			}
		    }
            }
        }
        out.print("\" ");
    }

    /*
     * 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(PrintWriter out, AResource subj, 
        AResource pred, AResource objRes, ALiteral objLit) 
    {
        printResource(out, subj);
        printResource(out, pred);
        if (objRes != null)
            printResource(out, objRes);
        else
            printNTripleLiteral(out, objLit);
        out.println(".");
    }

    /*
     * Create a HTML anchor from the URI or anonNode of the
     * given Resource
     *
     *@param r the Resource
     *@return the string as an HTML anchor
     */
    static private String addAnchor(AResource r) 
    {
        if (r.isAnonymous())
            return ANON_NODE + 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(PrintWriter out, AResource subj, 
        AResource pred, AResource objRes, ALiteral objLit, int num) 
    {
        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>");
    }

    private static class SH implements StatementHandler 
    {
        PrintWriter out;
        PrintWriter pw;
        boolean isNTriples;
        boolean printTriples;
        boolean printGraph;
        boolean anonNodesEmpty;
        int numStatements;
        int numLiterals;
	Hashtable subjects;
	int numSubjects;
  
        /*
         * Constructuor for the StatementHandler.  The primary
	 * responsiblitly is to cache init variables
         *
	 *@param out the servlet's PrintWriter
	 *@param pw the Dot file's PrintWriter
	 *  syntax; otherwise use HTML syntax
	 *@param isNTriples if true, output using the NTriples
	 *@param printTriples if true, print the triples
	 *@param printGraph if true, create the graph file
	 *@param printGraph if true, anonomyous nodes should be empty
	 */
        public SH(PrintWriter out, PrintWriter pw, boolean isNTriples, 
            boolean printTriples, boolean printGraph, boolean anonNodesEmpty)
        {
            this.out = out;
            this.pw = pw;
            this.isNTriples = isNTriples;
            this.printTriples = printTriples;
            this.printGraph = printGraph;
            this.anonNodesEmpty = anonNodesEmpty;

            this.numStatements = 0;
            this.numLiterals = 0;

	    this.subjects = new Hashtable();
	    this.numSubjects = 0;
        }

        /*
         * Generic handler for a Resource/Resource/Resource triple (S/P/O).
	 * Dispatches to the methods that do the real work.
         *
	 *@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 (printTriples)
                statementResource(subj, pred, obj);
	    if (printGraph)
	        statementDotResource(subj, pred, obj);
        }

        /*
         * Generic handler for a Resource/Resource/Resource triple (S/P/O).
	 * Dispatches to the methods that do the real work.
         *
	 *@param subj the subject
	 *@param pred the predicate
	 *@param obj the object (as a Literal)
	 */
        public void statement(AResource subj, AResource pred, ALiteral lit) 
        {
            numLiterals++;

	    if (printTriples)
                statementLiteral(subj, pred, lit);
	    if (printGraph)
                statementDotLiteral(subj, pred, lit);
        }

        /*
         * 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 statementResource(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 statementLiteral(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);
        }

	/* 
	 * Print the first part of a triple's Dot file.  See below for
	 * more info.  This is the same regardless if the triple's
	 * object is a Resource or a Literal
	 *
	 *@param subj the subject
	 */
        public void printFirstPart(AResource subj) 
	{
            if (subj.isAnonymous()) {
		if (this.anonNodesEmpty) {
		    Integer n = (Integer) subjects.get(subj.getAnonymousID());
		    if (n == null) {
			this.numSubjects++;
			subjects.put(subj.getAnonymousID(), new Integer(this.numSubjects));
                        this.pw.println("\"" + ANON_NODE + 
		            subj.getAnonymousID() + "\" [label=\"   \"];");
		    }
		}
                this.pw.print("\"" + ANON_NODE + subj.getAnonymousID());
            } else {
                this.pw.println("\"" + subj.getURI() + "\" [URL=\"" +
		    subj.getURI() + "\"];");
                this.pw.print("\"" + subj.getURI());
            }
	}

        /*
         * Handler for a Resource/Resource/Resource triple (S/P/O).
	 * Outputs the given triple using Dot syntax.
	 *
	 * Each triple will be output in three lines of DOT code as
	 * follows (not including the complication of anon nodes 
	 * and the possiblity that the anon nodes may be named 
	 * with an empty string):
	 *
	 *   1. "<subject>" [URL="<subject">];
	 *   2. "<subject>" -> "<object>" [label="<predicate>",URL="<predicate>"];
	 *   3. "<object>"  [URL="<object>"];
         *
	 *@param subj the subject
	 *@param pred the predicate
	 *@param obj the object (as a Resource)
	 */
        public void statementDotResource(AResource subj, AResource pred, AResource obj) 
        {
	    if (this.pw == null) return;

	    printFirstPart(subj);
           
            this.pw.print("\" -> ");

            if (obj.isAnonymous()) {
		if (this.anonNodesEmpty) {
                    this.pw.println("\"" + ANON_NODE + 
		        obj.getAnonymousID() + 
			"\" [label=\"" + pred.getURI() + "\",URL=\"" + 
			pred.getURI() + "\"];");
		} else {
                    this.pw.println("\"" + ANON_NODE + obj.getAnonymousID() + 
			"\" [label=\"" + pred.getURI() + "\",URL=\"" + 
			pred.getURI() + "\"];");
		}
            } else {
                this.pw.println("\"" + obj.getURI() + "\" [label=\"" +
		    pred.getURI() + "\",URL=\"" + pred.getURI() + "\"];");
		this.pw.println("\"" + obj.getURI() + "\" [URL=\"" +
		    obj.getURI() + "\"];");
	    }
        }

        /*
         * Handler for a Resource/Resource/Literal triple (S/P/O).
	 * Outputs the given triple using Dot syntax.
	 *
	 * Each triple will be output in three lines of DOT code as
	 * follows (not including the complication of anon nodes 
	 * and the possiblity that the anon nodes may be named 
	 * with an empty string):
         *
	 *   1. "<subject>" [URL="<subject">];
	 *   2. "<subject>" -> "<literal>" [label="<predicate>",URL="<predicate>"];
	 *   3. "<literal>" [shape="box"];
         *
	 *@param subj the subject
	 *@param pred the predicate
	 *@param obj the object (as a Literal)
	 */
        public void statementDotLiteral(AResource subj, AResource pred, ALiteral lit) 
        {
	    if (this.pw == null) return;

	    printFirstPart(subj);  // Same as Res/Res/Res

            /*
             * Before outputing the object (Literal) do the following:
             *
             * 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 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());

	    // Create a temporary label for the literal so that if
	    // it is duplicated it will be unique in the graph and
	    // thus have its own node.
	    String tmpName = "Literal_" + Integer.toString(this.numLiterals);
            this.pw.print("\" -> \"" + tmpName);

            this.pw.println("\" [label=\"" + pred.getURI() + 
			    "\",URL=\""    + pred.getURI() + "\"];");

            this.pw.println("\"" + tmpName + "\" [shape=box,label=\"" + tmpObject + "\"];");
        }
    }

    private void printErrorMessages(PrintWriter out, SaxErrorHandler eh)
    {
	try {
	    String s;

	    s = eh.getFatalErrors();
	    if (s != null && s.length() >= 1)
	        out.println("<h2>Fatal Error Messages</h2>" + s);

	    s = eh.getErrors();
	    if (s != null && s.length() >= 1)
	        out.println("<h2>Error Messages</h2>" + s);

	    s = eh.getWarnings();
	    if (s != null && s.length() >= 1)
	        out.println("<h2>Warning Messages</h2>" + s);
	} catch (Exception e) {
	    System.err.println(SERVLET_NAME + ": Error printing error messages.");
	}
    }

    /*
     * Initialize the graph output file.  If an error occurs, this
     * function will print an error message.
     *
     *@param out the servlet's output stream
     *@req the servlet request object
     *@return the File object for the graph file; null if an error occurs
     */
    private File initGraphFile(PrintWriter out, 
	HttpServletRequest req)
    {
        try {
            // Stop if any of the parameters are missing
            if (m_ServletTmpDir   == null || 
                m_GraphVizPath    == null || 
                m_GraphVizFontDir == 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_FONT_DIR  = " + m_GraphVizFontDir + " -->");

                out.println("<h1>Servlet initialization failed</h1>");
		out.println("Please send a message to <a href='mailto:" + MAIL_TO +  "'>" + MAIL_TO + "</a> and mention this problem.");
                return null;
            } 
	} catch (Exception e) {
	    System.err.println("Unable to create a temporary graph file. A graph cannot be generated.");
	    return null;
	}

        File dotFile = null;

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

	return dotFile;
    }

    /*
     * Check if the given URI is supported or not
     *
     *@param out the servlet's output stream
     *@param uri the URI to check
     *@return true if the URI is supported; false otherwise
     */
    private boolean isURISupported(PrintWriter out, String uri)
    {
	try {
	    if (uri.length() >= 4 && uri.substring(0,4).equalsIgnoreCase("file")) {
	        out.println("<h1>file URI Schemes are NOT Supported</h1>");
	        out.println("URIs from the 'file' URI scheme are not supported by this servlet.");
	        return false;
	    }
        } catch (Exception e) {
	    System.err.println("Exception in isURISupported.");
	    return false;
	}

	return true;
    }
    
    /*
     * 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 
    {
        res.setContentType ("text/html;charset=utf-8");
        PrintWriter out = res.getWriter ();

	String sSaveRDF         = req.getParameter (SAVE_RDF);
	String sSaveDOTFile     = req.getParameter (SAVE_DOT_FILE);
	String sFormat          = req.getParameter (FORMAT);
	String sNTriples        = req.getParameter (NTRIPLES);
	String sEmbedded        = req.getParameter (EMBEDDED_RDF);
	String sTriplesAndGraph = req.getParameter (TRIPLES_AND_GRAPH);
	String sAnonNodesEmpty  = req.getParameter (ANON_NODES_EMPTY);

        // Set the print flags
        boolean printTriples = true;
	boolean printGraph = true;
	if (sTriplesAndGraph != null) {
	    if (sTriplesAndGraph.equals(PRINT_TRIPLES)) 
		printGraph = false;
	    if (sTriplesAndGraph.equals(PRINT_GRAPH))
		printTriples = false;
	} 

	// Determine if printing the triples and/or graph
        boolean anonNodesEmpty = (sAnonNodesEmpty != null) ? true : false;
        boolean nTriples = (sNTriples != null) ? true : false;

        // ARP parser has embedded = true by default so if user
        // wants embedding, must set it to false
        boolean embedded = (sEmbedded != null) ? false : true;

        String xmlBase = DEFAULT_NAMESPACE;

        printDocumentHeader (out);
        
        if (sURI != null && sURI.length() >= 1) {

	    // First check for unsupported URIs
	    if (!isURISupported(out, sURI)) {
                printDocumentFooter(out, null);
                return;
	    }

            xmlBase = sURI;
	    try {
		sRDF = getRDFfromURI(sURI);
		if (sRDF == null)
		    throw new getRDFException("The URI may not exist or the server is down.@@");
	    } catch (getRDFException e) {
		out.println("<h1>RDF Load Error</h1>");
		out.println("An attempt to load the RDF from URI '" + sURI +
			    "' failed.  (" + e.getMessage() + ")");
		printDocumentFooter(out, null);
		return;
	    }
        }
 
	PrintWriter pw = null; // The writer for the graph file
	File dotFile = null;   // The graph file
        if (sFormat != null && printGraph) {
	    dotFile = initGraphFile(out, req);
	    if (dotFile == null)
		// Assume error has been reported
		return;

            // Create a PrintWriter for the DOT handler
            FileWriter fw = new FileWriter(dotFile);
	    if (fw != null)
                pw = new PrintWriter(fw);
	    if (pw != null)
                // Add the graph header
                processGraphParameters (req, pw);
	}

	// Create the StatementHandler - it will handle triples for
	// the table/ntriples and the graph file
        SH sh = new SH(out, pw, nTriples, printTriples, printGraph, anonNodesEmpty);

        // Create the ErrorHandler 
        SaxErrorHandler errorHandler = new SaxErrorHandler(out, false);

        // Create and initialize the parser
	ARP parser = new com.hp.hpl.jena.rdf.arp.ARP();
        parser.setErrorHandler(errorHandler);
        parser.setStatementHandler(sh);
        parser.setEmbedding(embedded);

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

        if (printTriples)
            printTripleTableHeader (out, nTriples);

        try {
	    StringReader  sr = new StringReader (sRDF);
	    parser.load(sr, xmlBase);
        } catch (Exception ex) {
	    out.println ("<h1>Parser Loading Error</h1>");
	    out.println ("Exception parsing: " + sURI + ": " + ex.toString());
            printDocumentFooter(out, null);
            return;
        }

        if (printTriples)
            printTripleTableFooter(out, nTriples);

	printErrorMessages(out, errorHandler);

        res.flushBuffer();
        if (sFormat != null && printGraph) {
            generateGraph(out, pw, dotFile, sRDF, req, sFormat,
                          (sSaveRDF != null) ? true : false,
                          (sSaveDOTFile != null && sSaveDOTFile.equals ("on") ? true : false));
        }


        if (sURI != null && sURI.length() >= 1)
            printDocumentFooter(out, null);
        else
            printDocumentFooter(out, sRDF);
    }
}

Webmaster