Package org.gomba

Source Code of org.gomba.ErrorServlet$ErrorRequestXMLReader

package org.gomba;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
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.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
* Servlet implemetation to be used as an "error page".
*
* <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>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>
*
* </dl>
*
* @author Flavio Tordini
* @version $Id: ErrorServlet.java,v 1.2 2005/07/06 10:25:00 flaviotordini Exp $
*/
public class ErrorServlet extends GenericServlet {

    private final static String EXCEPTION_ATTRIBUTE = "javax.servlet.error.exception";

    private final static String INIT_PARAM_JAVA_FEATURES = "java-specific";

    private final static String DEFAULT_XSLT = "/org/gomba/errorServlet.xslt";

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

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

    /**
     * Wheter to include Java-specific information in the error XML
     */
    private boolean javaFeatures;

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

        // Java specific features
        String javaFeaturesStr = config
                .getInitParameter(INIT_PARAM_JAVA_FEATURES);
        if (javaFeaturesStr != null) {
            this.javaFeatures = Boolean.valueOf(javaFeaturesStr).booleanValue();
        }

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

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

        // XSLT
        final String xsltStyleSheet = config.getInitParameter("xslt");

        InputStream is;
        if (xsltStyleSheet != null) {
            // Create a templates object, which is the processed,
            // thread-safe representation of the stylesheet.
            is = getServletContext().getResourceAsStream(xsltStyleSheet);
        } else {
            is = ErrorServlet.class.getResourceAsStream(DEFAULT_XSLT);
        }

        if (is == null) {
            throw new ServletException("Cannot find stylesheet: "
                    + xsltStyleSheet);
        }
        try {
            TransformerFactory tfactory = TransformerFactory.newInstance();
            Source xslSource = new StreamSource(is);
            if (xsltStyleSheet != null) {
                // 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 = buildXsltFixedParams(xsltParams);
            } catch (Exception e) {
                throw new ServletException("Error parsing XSLT params: "
                        + xsltParams, e);
            }
        }

    }

    private static Map buildXsltFixedParams(String xsltParams)
            throws IOException {
        Properties parameters = new Properties();

        InputStream inputStream = new ByteArrayInputStream(xsltParams
                .getBytes());
        try {
            parameters.load(inputStream);
        } finally {
            inputStream.close();
        }

        return parameters;
    }

    /**
     * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest,
     *           javax.servlet.ServletResponse)
     */
    public void service(ServletRequest request, ServletResponse response)
            throws ServletException, IOException {

        // get the exception for the request
        Throwable throwable = (Throwable) request
                .getAttribute(EXCEPTION_ATTRIBUTE);
        if (throwable == null) {
            throw new ServletException("Missing " + EXCEPTION_ATTRIBUTE
                    + " request attribute");
        }

        // generate XML
        try {
            serializeXML(throwable, response);
        } catch (Exception e) {
            throw new ServletException(
                    "Error rendering error XML representation.", e);
        }

    }

    /**
     * Serialize a <code>Throwable</code> object to XML using SAX and TrAX
     * APIs in a smart way. Dagnelo, you're a sucker!
     *
     * @param throwable
     *                   The <code>Throwable</code> object to serialize
     * @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(Throwable throwable, ServletResponse response)
            throws Exception {

        // Let the HTTP client know the output content type
        if (this.mediaType != null) {
            response.setContentType(this.mediaType);
        } else {
            response.setContentType("text/xml");
        }

        // 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
        t.setOutputProperty(OutputKeys.ENCODING, response
                .getCharacterEncoding());
        // DTD
        if (this.doctypePublic != null) {
            t.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, this.doctypePublic);
        }
        if (this.doctypeSystem != null) {
            t.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, this.doctypeSystem);
        }

        // Create the trasformation source using our custom ObjectInputSource
        InputSource inputSource = new ObjectInputSource(throwable);

        // Create the sax "parser".
        ObjectXMLReader saxReader = new ErrorRequestXMLReader();

        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);

    }

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

        private final static String ROOT_ELEMENT = "error";

        private final static String STACK_TRACE_ELEMENT = "stackTrace";

        private final static String STACK_TRACE_ITEM_ELEMENT = "element";

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

            Throwable throwable = (Throwable) input.getObject();

            this.handler.startDocument();
            exceptionEvents(throwable, null);
            this.handler.endDocument();
        }

        /**
         * Recursive method that fires SAX events for a Throwable object and its
         * causes.
         */
        private void exceptionEvents(Throwable throwable,
                StackTraceElement[] parentStrackTrace) throws SAXException {
            this.handler.startElement(ContentHandlerUtils.DUMMY_NSU,
                    ROOT_ELEMENT, ROOT_ELEMENT, ContentHandlerUtils.DUMMY_ATTS);

            ContentHandlerUtils.tag(this.handler, "message", throwable
                    .getMessage());

            StackTraceElement[] ste = null;
            if (ErrorServlet.this.javaFeatures) {

                ContentHandlerUtils.tag(this.handler, "type", throwable
                        .getClass().getName());

                // stack trace
                ste = throwable.getStackTrace();

                // limit stack trace the same way printStackTrace() does
                // lifted and adapted from java.lang.Throwable
                // let's hope Sun does not sue me!
                int usefulFrames = ste.length - 1;
                if (parentStrackTrace != null) {
                    // Compute number of frames in common between this and
                    // caused exception
                    int n = parentStrackTrace.length - 1;
                    while (usefulFrames >= 0 && n >= 0
                            && ste[usefulFrames].equals(parentStrackTrace[n])) {
                        usefulFrames--;
                        n--;
                    }
                }

                this.handler.startElement(ContentHandlerUtils.DUMMY_NSU,
                        STACK_TRACE_ELEMENT, STACK_TRACE_ELEMENT,
                        ContentHandlerUtils.DUMMY_ATTS);

                for (int i = 0; i <= usefulFrames; i++) {

                    this.handler.startElement(ContentHandlerUtils.DUMMY_NSU,
                            STACK_TRACE_ITEM_ELEMENT, STACK_TRACE_ITEM_ELEMENT,
                            ContentHandlerUtils.DUMMY_ATTS);

                    ContentHandlerUtils.tag(this.handler, "type", ste[i]
                            .getClassName());

                    ContentHandlerUtils.tag(this.handler, "method", ste[i]
                            .getMethodName());

                    ContentHandlerUtils.tag(this.handler, "file", ste[i]
                            .getFileName());

                    ContentHandlerUtils.tag(this.handler, "line", Integer
                            .toString(ste[i].getLineNumber()));

                    if (ste[i].isNativeMethod()) {
                        ContentHandlerUtils.tag(this.handler, "native", Boolean
                                .toString(ste[i].isNativeMethod()));
                    }

                    this.handler.endElement(ContentHandlerUtils.DUMMY_NSU,
                            STACK_TRACE_ITEM_ELEMENT, STACK_TRACE_ITEM_ELEMENT);

                }

                this.handler.endElement(ContentHandlerUtils.DUMMY_NSU,
                        STACK_TRACE_ELEMENT, STACK_TRACE_ELEMENT);

            }

            // recurse!
            Throwable cause = throwable.getCause();
            if (cause != null) {
                exceptionEvents(cause, ste);
            }

            this.handler.endElement(ContentHandlerUtils.DUMMY_NSU,
                    ROOT_ELEMENT, ROOT_ELEMENT);
        }
    }

}
TOP

Related Classes of org.gomba.ErrorServlet$ErrorRequestXMLReader

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.