Package org.gomba

Source Code of org.gomba.XMLServlet$EmptyXMLReader

package org.gomba;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.sql.Clob;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.SimpleDateFormat;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.gomba.utils.xml.ContentHandlerUtils;
import org.gomba.utils.xml.ObjectInputSource;
import org.gomba.utils.xml.ObjectXMLReader;
import org.gomba.utils.xml.XMLTextReader;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
* Render data accessed via JDBC in XML syntax. The SQL in the
* <code>query</code> init-param should be a SELECT and must return a
* resultset. This servlet inherits the init-params of
* {@link org.gomba.AbstractServlet}, plus:
*
* <dl>
*
* <dt>doctype-public</dt>
* <dd>Specifies the public identifier to be used in the document type
* declaration. If the doctype-system init-param is not set, then the
* doctype-public init-param is ignored. (Optional)</dd>
*
* <dt>doctype-system</dt>
* <dd>Specifies the system identifier to be used in the document type
* declaration. (Optional)</dd>
*
* <dt>media-type</dt>
* <dd>The resource MIME content type. Defaults to "text/xml" (Optional)</dd>
*
* <dt>root-element</dt>
* <dd>The output XML root element name. Defaults to "resultSet" (Optional)
* </dd>
*
* <dt>row-element</dt>
* <dd>The output XML row element name. If an empty value is specified the row
* element is omitted. This is useful for queries that always return a single
* row. Defaults to "row" (Optional)</dd>
*
* <dt>xslt</dt>
* <dd>The XSLT stylesheet to apply to the default XML output. (Optional)</dd>
*
* <dt>xslt-params</dt>
* <dd>XSLT parameters in Java Properties format. (Optional)</dd>
*
* <dt>xslt-output-properties</dt>
* <dd>XSLT output properties in Java Properties format.
* http://www.w3.org/TR/xslt#output (Optional)</dd>
*
* <dt>column-case</dt>
* <dd>The case of XML element names. May be 'lower', 'upper' or 'original'.
* Default: 'lower' (Optional)</dd>
*
* </dl>
*
* <p>
* This servlet can handle the following HTTP methods: GET, HEAD.
* </p>
*
* <p>
* The row element children are named after the corresponding JDBC column. If a
* column name in your db is not a valid XML element name or you would like to
* use a different name in the XML, just use the <code>AS</code> SQL keyword
* to change it. It is the user responsibility to create a DTD or XML Schema for
* the generated XML.
* </p>
*
* @author Flavio Tordini
* @version $Id: XMLServlet.java,v 1.15 2005/12/03 15:47:11 flaviotordini Exp $
*/
public class XMLServlet extends SingleQueryServlet {

    private final static String ELEMENT_ROOT = "resultSet";

    private final static String ELEMENT_ROW = "row";

    private static final String PATTERN_TIMESTAMP = "yyyy-MM-dd'T'HH:mm:ss";

    private static final String PATTERN_DATE = "yyyy-MM-dd";

    private static final String PATTERN_TIME = "HH:mm:ss";

    private static final int CASE_ORIGINAL = 1;

    private static final int CASE_UPPER = 2;

    private static final int CASE_LOWER = 3;

    /**
     * DTD public identifier, if any.
     */
    private String doctypePublic;

    /**
     * DTD system identifier, if any.
     */
    private String doctypeSystem;

    /**
     * The resource MIME content type, if any.
     */
    private String mediaType;

    /**
     * The parsed XSLT stylesheet, if any.
     */
    private Templates templates;

    /**
     * The element names.
     */
    private String rootElementName, rowElementName;

    /**
     * The case of the resultset colums.
     */
    private int columnCase;

    /**
     * XSLT parameters.
     */
    private Map xsltFixedParameters;

    /**
     * XSLT output properties.
     */
    private Properties xsltOutputProperties;

    /**
     * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
     */
    public void init(ServletConfig config) throws ServletException {
        super.init(config);

        // DTD
        this.doctypePublic = config.getInitParameter("doctype-public");
        this.doctypeSystem = config.getInitParameter("doctype-system");

        // MIME
        this.mediaType = config.getInitParameter("media-type");

        // column case
        String ccase = config.getInitParameter("column-case");
        if (ccase == null) {
            // default
            this.columnCase = CASE_LOWER;
        } else if (ccase.equals("lower")) {
            this.columnCase = CASE_LOWER;
        } else if (ccase.equals("upper")) {
            this.columnCase = CASE_UPPER;
        } else if (ccase.equals("original")) {
            this.columnCase = CASE_ORIGINAL;
        } else {
            throw new ServletException("Invalid 'column-case' value: " + ccase);
        }

        // element names
        this.rootElementName = config.getInitParameter("root-element");
        if (this.rootElementName == null) {
            this.rootElementName = ELEMENT_ROOT;
        }
        this.rowElementName = config.getInitParameter("row-element");
        if (this.rowElementName == null) {
            this.rowElementName = ELEMENT_ROW;
        }

        // XSLT
        final String xsltStyleSheet = config.getInitParameter("xslt");
        if (xsltStyleSheet != null) {
            // Create a templates object, which is the processed,
            // thread-safe representation of the stylesheet.
            InputStream is = getServletContext().getResourceAsStream(
                    xsltStyleSheet);
            if (is == null) {
                throw new ServletException("Cannot find stylesheet: "
                        + xsltStyleSheet);
            }
            try {
                TransformerFactory tfactory = TransformerFactory.newInstance();
                Source xslSource = new StreamSource(is);
                // Note that if we don't do this, relative URLs can not be
                // resolved correctly!
                xslSource.setSystemId(getServletContext().getRealPath(
                        xsltStyleSheet));
                this.templates = tfactory.newTemplates(xslSource);
            } catch (TransformerConfigurationException tce) {
                throw new ServletException("Error parsing XSLT stylesheet: "
                        + xsltStyleSheet, tce);
            }

            // create a map of fixed xslt parameters
            final String xsltParams = config.getInitParameter("xslt-params");
            if (xsltParams != null) {
                try {
                    this.xsltFixedParameters = stringToProperties(xsltParams);
                } catch (Exception e) {
                    throw new ServletException("Error parsing XSLT params: "
                            + xsltParams, e);
                }
            }

            // create a map of xslt output properties
            final String xsltOutputProperties = config
                    .getInitParameter("xslt-output-properties");
            if (xsltOutputProperties != null) {
                try {
                    this.xsltOutputProperties = stringToProperties(xsltOutputProperties);
                } catch (Exception e) {
                    throw new ServletException(
                            "Error parsing XSLT output properties: "
                                    + xsltOutputProperties, e);
                }
            }

        }

    }

    /**
     * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
     *      javax.servlet.http.HttpServletResponse)
     */
    protected final void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        processRequest(request, response, true);
    }

    /**
     * @see javax.servlet.http.HttpServlet#doHead(javax.servlet.http.HttpServletRequest,
     *      javax.servlet.http.HttpServletResponse)
     */
    protected final void doHead(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        processRequest(request, response, false);
    }

    /**
     * @see org.gomba.AbstractServlet#doOutput(java.sql.ResultSet,
     *      javax.servlet.http.HttpServletResponse, ParameterResolver)
     */
    protected void doOutput(ResultSet resultSet, HttpServletResponse response,
            ParameterResolver parameterResolver) throws Exception {
        // Create the sax "parser".
        ObjectXMLReader saxReader = new ResultSetXMLReader();
        // generate XML and write it to the response body
        serializeXML(resultSet, saxReader, response);
    }

    /**
     * @see org.gomba.AbstractServlet#doDefaultOutput(javax.servlet.http.HttpServletResponse)
     */
    protected void doDefaultOutput(HttpServletResponse response)
            throws Exception {
        // Create a dummy sax "parser".
        ObjectXMLReader saxReader = new EmptyXMLReader();
        // generate XML and write it to the response body
        serializeXML(null, saxReader, response);
    }

    /**
     * Serialize an object to XML using SAX and TrAX APIs in a smart way.
     *
     * @param object
     *            The object to serialize
     * @param saxReader
     *            The SAX "parser"
     * @param response
     *            The HTTP response
     * @see <a
     *      href="http://java.sun.com/j2se/1.4.2/docs/api/javax/xml/transform/package-summary.html">TrAX
     *      API </a>
     */
    private void serializeXML(Object object, ObjectXMLReader saxReader,
            HttpServletResponse response) throws Exception {

        // Let the HTTP client know the output content type
        String mediaType = null;
        if (this.mediaType != null) {
            mediaType = this.mediaType;
        }
        if (mediaType == null && this.xsltOutputProperties != null) {
            mediaType = this.xsltOutputProperties
                    .getProperty(OutputKeys.MEDIA_TYPE);
        }
        if (mediaType == null && this.templates != null) {
                mediaType = this.templates.getOutputProperties().getProperty(
                        OutputKeys.MEDIA_TYPE);
        }
        if (mediaType == null) {
            mediaType = "text/xml";
        }
        response.setContentType(mediaType);

        // Create TrAX Transformer
        Transformer t;
        if (this.templates != null) {
            // Create a transformer using our stylesheet
            t = this.templates.newTransformer();

            // pass fixed XSLT parameters
            if (this.xsltFixedParameters != null) {
                for (Iterator i = this.xsltFixedParameters.entrySet()
                        .iterator(); i.hasNext();) {
                    Map.Entry mapEntry = (Map.Entry) i.next();
                    t.setParameter((String) mapEntry.getKey(), mapEntry
                            .getValue());
                }
            }

            // TODO maybe we could also pass some dynamic values such as the
            // request param or path info. But let's wait until the need
            // arises...

        } else {
            // Create an "identity" transformer - copies input to output
            t = TransformerFactory.newInstance().newTransformer();
        }

        // Set trasformation output properties

        // DTD
        if (this.doctypePublic != null) {
            t.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, this.doctypePublic);
        }
        if (this.doctypeSystem != null) {
            t.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, this.doctypeSystem);
        }

        // set XSLT output properties
        if (this.xsltOutputProperties != null) {
            t.setOutputProperties(this.xsltOutputProperties);
        }

        // Output encoding
        String preferredEncoding = t.getOutputProperties().getProperty(
                OutputKeys.ENCODING);
        if (preferredEncoding != null) {
            response.setCharacterEncoding(preferredEncoding);
        }

        // Create the trasformation source using our custom ObjectInputSource
        InputSource inputSource = new ObjectInputSource(object);
        Source source = new SAXSource(saxReader, inputSource);

        // Create the trasformation result
        // Result result = new StreamResult(response.getWriter());
        Result result = new StreamResult(response.getOutputStream());

        // Go!
        t.transform(source, result);

    }

    private static Properties stringToProperties(String string)
            throws IOException {
        Properties properties = new Properties();
        InputStream inputStream = new ByteArrayInputStream(string.getBytes());
        try {
            properties.load(inputStream);
        } finally {
            inputStream.close();
        }
        return properties;
    }

    /**
     * This SAX XMLReader generates an XML document from a JDBC Resultset.
     */
    final class ResultSetXMLReader extends ObjectXMLReader {

        /**
         * @see org.gomba.utils.xml.ObjectXMLReader#parse(org.gomba.utils.xml.ObjectInputSource)
         */
        public void parse(ObjectInputSource input) throws IOException,
                SAXException {

            try {

                // SimpleDateFormat objects are lazily instantiated
                // Note that SimpleDateFormat objects cannot be used
                // concurrently by multiple threads
                SimpleDateFormat timestampFormatter = null;
                SimpleDateFormat dateFormatter = null;
                SimpleDateFormat timeFormatter = null;

                ResultSet resultSet = (ResultSet) input.getObject();

                this.handler.startDocument();
                this.handler.startElement(ContentHandlerUtils.DUMMY_NSU,
                        XMLServlet.this.rootElementName,
                        XMLServlet.this.rootElementName,
                        ContentHandlerUtils.DUMMY_ATTS);

                final ResultSetMetaData rsmd = resultSet.getMetaData();

                // Get an array of column names
                // It is user responsibility to ensure column names are valid
                // XML element names
                final String[] columns = new String[rsmd.getColumnCount()];
                for (int i = 0; i < columns.length; i++) {
                    // The set of column names begins with '1' rather than '0'
                    String columnName = rsmd.getColumnName(i + 1);
                    if (XMLServlet.this.columnCase == CASE_LOWER) {
                        columns[i] = columnName.toLowerCase();
                    } else if (XMLServlet.this.columnCase == CASE_ORIGINAL) {
                        columns[i] = columnName;
                    } else if (XMLServlet.this.columnCase == CASE_UPPER) {
                        columns[i] = columnName.toUpperCase();
                    } else {
                        throw new SAXException(
                                "Can't happen! Invalid column-case: "
                                        + XMLServlet.this.columnCase);
                    }
                }

                // Get an array of the types of each column
                final int[] columnTypes = new int[columns.length];
                for (int i = 0; i < columnTypes.length; i++) {
                    // The set of column types also begins with '1' rather than
                    // '0'
                    columnTypes[i] = rsmd.getColumnType(i + 1);
                }

                do {

                    // if the user specified an empty row element name, just
                    // omit the element.
                    if (XMLServlet.this.rowElementName.length() > 0) {
                        this.handler.startElement(
                                ContentHandlerUtils.DUMMY_NSU,
                                XMLServlet.this.rowElementName,
                                XMLServlet.this.rowElementName,
                                ContentHandlerUtils.DUMMY_ATTS);
                    }

                    for (int i = 0; i < columns.length; i++) {
                        switch (columnTypes[i]) {

                        case Types.DATE:
                            // format date only
                            java.sql.Date date = resultSet.getDate(i + 1);
                            if (date != null) {
                                if (dateFormatter == null) {
                                    dateFormatter = new SimpleDateFormat(
                                            PATTERN_DATE);
                                }
                                String d = dateFormatter.format(date);
                                ContentHandlerUtils.tag(this.handler,
                                        columns[i], d);
                            }
                            break;

                        case Types.TIME:
                            // format time only
                            java.sql.Time time = resultSet.getTime(i + 1);
                            if (time != null) {
                                if (timeFormatter == null) {
                                    timeFormatter = new SimpleDateFormat(
                                            PATTERN_TIME);
                                }
                                String d = timeFormatter.format(time);
                                ContentHandlerUtils.tag(this.handler,
                                        columns[i], d);
                            }
                            break;

                        case Types.TIMESTAMP:
                            Timestamp timestamp = resultSet.getTimestamp(i + 1);
                            if (timestamp != null) {
                                if (timestampFormatter == null) {
                                    timestampFormatter = new SimpleDateFormat(
                                            PATTERN_TIMESTAMP);
                                }
                                String d = timestampFormatter.format(timestamp);
                                ContentHandlerUtils.tag(this.handler,
                                        columns[i], d);
                            }
                            break;

                        case Types.VARCHAR:
                        case Types.CHAR:
                        case Types.LONGVARCHAR:
                            Reader reader = resultSet.getCharacterStream(i + 1);
                            readerToXml(reader, columns[i]);
                            break;

                        case Types.CLOB:
                            Clob clob = resultSet.getClob(i + 1);
                            if (clob != null) {
                                Reader clobReader = clob.getCharacterStream();
                                readerToXml(clobReader, columns[i]);
                            }
                            break;

                        // TODO base64 BLOBs?

                        default:
                            Object object = resultSet.getObject(i + 1);
                            if (object != null) {
                                ContentHandlerUtils.tag(this.handler,
                                        columns[i], object.toString());
                            }

                        }
                    }

                    if (XMLServlet.this.rowElementName.length() > 0) {
                        this.handler.endElement(ContentHandlerUtils.DUMMY_NSU,
                                XMLServlet.this.rowElementName,
                                XMLServlet.this.rowElementName);
                    }

                } while (resultSet.next());

                this.handler.endElement(ContentHandlerUtils.DUMMY_NSU,
                        XMLServlet.this.rootElementName,
                        XMLServlet.this.rootElementName);
                this.handler.endDocument();

            } catch (SQLException sqle) {
                throw new SAXException("SQL Error.", sqle);
            }
        }

        /**
         * Consume a Reader and generate SAX events.
         *
         * @param reader
         *            A Reader instance, maybe null.
         * @param columnName
         *            The name of the table column.
         */
        private void readerToXml(Reader reader, String columnName)
                throws SAXException, IOException {
            if (reader != null) {
                try {
                    // wrap our reader in order to strip invalid XML chars
                    reader = new XMLTextReader(reader);
                    this.handler.startElement(ContentHandlerUtils.DUMMY_NSU,
                            columnName, columnName,
                            ContentHandlerUtils.DUMMY_ATTS);

                    char[] buffer = new char[4096];
                    int length;
                    while ((length = reader.read(buffer)) >= 0) {
                        this.handler.characters(buffer, 0, length);
                    }
                    this.handler.endElement(ContentHandlerUtils.DUMMY_NSU,
                            columnName, columnName);
                } finally {
                    reader.close();
                }
            }
        }

    }

    /**
     * This SAX XMLReader generates an empty XML document. This is used for
     * generating a dummy default document.
     */
    final class EmptyXMLReader extends ObjectXMLReader {

        /**
         * @see org.gomba.utils.xml.ObjectXMLReader#parse(org.gomba.utils.xml.ObjectInputSource)
         */
        public void parse(ObjectInputSource input) throws IOException,
                SAXException {
            this.handler.startDocument();
            this.handler.startElement(ContentHandlerUtils.DUMMY_NSU,
                    XMLServlet.this.rootElementName,
                    XMLServlet.this.rootElementName,
                    ContentHandlerUtils.DUMMY_ATTS);
            this.handler.endElement(ContentHandlerUtils.DUMMY_NSU,
                    XMLServlet.this.rootElementName,
                    XMLServlet.this.rootElementName);
            this.handler.endDocument();
        }

    }
}
TOP

Related Classes of org.gomba.XMLServlet$EmptyXMLReader

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.