org.apache.click.util.ErrorReport.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.click.util.ErrorReport.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.click.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.StringTokenizer;
import java.util.TreeMap;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.click.Page;
import org.apache.click.service.TemplateException;
import org.apache.commons.lang.StringUtils;

/**
 * Provides an HTML <div> error report for the display of page error
 * information. This class is used by ErrorPage and ClickServlet for the
 * display of error information.
 */
public class ErrorReport {

    /** The Java language keywords. Used to render Java source code. */
    private static final String[] JAVA_KEYWORDS = { "package", "import", "class", "public", "protected", "private",
            "extends", "implements", "return", "if", "while", "for", "do", "else", "try", "new", "void", "catch",
            "throws", "throw", "static", "final", "break", "continue", "super", "finally", "true", "false", "true;",
            "false;", "null", "boolean", "int", "char", "long", "float", "double", "short" };

    /** The column number of the error, or -1 if not defined. */
    protected int columnNumber;

    /** The cause of the error. */
    protected final Throwable error;

    /** The line number of the error, or -1 if not defined. */
    protected int lineNumber;

    /** The error is Velocity parsing exception. */
    protected final boolean isParseError;

    /** The application is in "production" mode flag. */
    protected final boolean isProductionMode;

    /** The page class which caused the error. */
    protected final Class<? extends Page> pageClass;

    /** The servlet request. */
    protected final HttpServletRequest request;

    /** The servlet context. */
    protected final ServletContext servletContext;

    /** The name of the error source. */
    protected final String sourceName;

    /** The error source LineNumberReader. */
    protected LineNumberReader sourceReader;

    // Constructor ------------------------------------------------------------

    /**
     * Create a ErrorReport instance from the given error and page.
     *
     * @param error the cause of the error
     * @param pageClass the Page class which caused the error
     * @param isProductionMode the application is in "production" mode
     * @param request the page request
     * @param servletContext the servlet context
     */
    public ErrorReport(Throwable error, Class<? extends Page> pageClass, boolean isProductionMode,
            HttpServletRequest request, ServletContext servletContext) {

        this.error = error;
        this.pageClass = pageClass;
        this.isProductionMode = isProductionMode;
        this.request = request;
        this.servletContext = servletContext;

        if (error instanceof TemplateException && ((TemplateException) error).isParseError()) {

            TemplateException te = (TemplateException) error;

            if (te.getTemplateName().charAt(0) == '/') {
                sourceName = te.getTemplateName();
            } else {
                sourceName = '/' + te.getTemplateName();
            }
            lineNumber = te.getLineNumber();
            columnNumber = te.getColumnNumber();

            InputStream is = servletContext.getResourceAsStream(sourceName);

            sourceReader = new LineNumberReader(new InputStreamReader(is));
            isParseError = true;

        } else {
            isParseError = false;
            sourceName = null;
            columnNumber = -1;

            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            getCause().printStackTrace(pw);

            StringTokenizer tokenizer = new StringTokenizer(sw.toString(), "\n");

            try {
                tokenizer.nextToken();
                String line = tokenizer.nextToken();
                if (getCause() instanceof IllegalArgumentException) {
                    line = tokenizer.nextToken();

                    if (line.indexOf("org.apache.commons.lang.Validate") != -1) {
                        line = tokenizer.nextToken();
                    }
                }

                int nameStart = line.indexOf("at ");
                int nameEnd = line.indexOf("(");
                nameEnd = line.lastIndexOf(".", nameEnd);
                if (line.indexOf("$") != -1) {
                    nameEnd = line.indexOf("$");
                }

                if (nameStart != -1 && nameEnd != -1) {
                    String classname = line.substring(nameStart + 3, nameEnd);

                    int lineStart = line.indexOf(":");
                    if (lineStart != -1) {
                        int lineEnd = line.indexOf(")");
                        String lineNumber = line.substring(lineStart + 1, lineEnd);

                        this.lineNumber = Integer.parseInt(lineNumber);

                        String filename = "/" + classname.replace('.', '/') + ".java";

                        sourceReader = getJavaSourceReader(filename);
                    }
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    // Public Methods ---------------------------------------------------------

    /**
     * Return a error report HTML &lt;div&gt; element for the given error and
     * page. The HTML &lt;div&gt; element 'id' and 'class' attribute values are
     * 'errorReport'.
     *
     * @return a error HTML display string
     */
    @SuppressWarnings("unchecked")
    public String toString() {

        if (isProductionMode()) {
            Locale locale = request.getLocale();
            ResourceBundle bundle = ClickUtils.getBundle("click-control", locale);
            return bundle.getString("production-error-message");
        }

        HtmlStringBuffer buffer = new HtmlStringBuffer(10 * 1024);

        Throwable cause = getCause();

        buffer.append("<div id='errorReport' class='errorReport'>\n");

        // Exception table
        buffer.append(
                "<table border='1' cellspacing='1' cellpadding='4' width='100%' style='background-color: white;'>");
        if (isParseError()) {
            buffer.append(
                    "<tr><td colspan='2' style='color:white; background-color: navy; font-weight: bold'>Page Parsing Error</td></tr>");
            buffer.append("<tr><td width='12%'><b>Source</b></td><td>");
            buffer.append(getSourceName());
            buffer.append("</td></tr>");
        } else {
            buffer.append(
                    "<tr><td colspan='2' style='color:white; background-color: navy; font-weight: bold'>Exception</td></tr>");
            buffer.append("<tr><td width='12%'><b>Class</b></td><td>");
            buffer.append(cause.getClass().getName());
            buffer.append("</td></tr>");
        }
        buffer.append("<tr><td valign='top' width='12%'><b>Message</b></td><td>");
        buffer.append(getMessage());
        buffer.append("</td></tr>");
        if (getSourceReader() != null) {
            buffer.append("<tr><td valign='top' colspan='2'>\n");
            buffer.append(getRenderedSource());
            buffer.append("</td></tr>");
        }
        if (!isParseError()) {
            buffer.append("<tr><td valign='top' colspan='2'>\n");
            buffer.append(getStackTrace());
            buffer.append("</td></tr>");
        }
        buffer.append("</table>");
        buffer.append("<br/>");

        // Page table
        buffer.append(
                "<table border='1' cellspacing='1' cellpadding='4' width='100%' style='background-color: white;'>");
        buffer.append(
                "<tr><td colspan='2' style='color:white; background-color: navy; font-weight: bold'>Page</td></tr>");
        buffer.append("<tr><td width='12%'><b>Classname</b></td><td>");
        if (pageClass != null) {
            buffer.append(pageClass.getName());
        }
        buffer.append("</td></tr>");
        buffer.append("<tr><td width='12%'><b>Path</b></td><td>");
        buffer.append(ClickUtils.getResourcePath(request));
        buffer.append("</td></tr>");
        buffer.append("</table>");
        buffer.append("<br/>");

        // Request table
        buffer.append(
                "<table border='1' cellspacing='1' cellpadding='4' width='100%' style='background-color: white;'>");
        buffer.append(
                "<tr><td colspan='2' style='color:white; background-color: navy; font-weight: bold'>Request</td></tr>");

        Map<String, Object> requestAttributes = new TreeMap<String, Object>();
        Enumeration attributeNames = request.getAttributeNames();
        while (attributeNames.hasMoreElements()) {
            String name = attributeNames.nextElement().toString();
            requestAttributes.put(name, request.getAttribute(name));
        }
        buffer.append("<tr><td width='12%' valign='top'><b>Attributes</b></td><td>");
        writeMap(requestAttributes, buffer);
        buffer.append("</td></tr>");

        buffer.append("<tr><td width='12%'><b>Auth Type</b></td><td>");
        buffer.append(request.getAuthType());
        buffer.append("</td></tr>");

        buffer.append("<tr><td width='12%'><b>Context Path</b></td><td>");
        buffer.append("<a href='");
        buffer.append(request.getContextPath());
        buffer.append("'>");
        buffer.append(request.getContextPath());
        buffer.append("</a>");
        buffer.append("</td></tr>");

        Map<String, Object> requestHeaders = new TreeMap<String, Object>();
        Enumeration headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String name = headerNames.nextElement().toString();
            requestHeaders.put(name, request.getHeader(name));
        }
        buffer.append("<tr><td width='12%' valign='top'><b>Headers</b></td><td>");
        writeMap(requestHeaders, buffer);
        buffer.append("</td></tr>");

        buffer.append("<tr><td width='12%'><b>Query</b></td><td>");
        buffer.append(request.getQueryString());
        buffer.append("</td></tr>");

        buffer.append("<tr><td width='12%'><b>Method</b></td><td>");
        buffer.append(request.getMethod());
        buffer.append("</td></tr>");

        Map<String, Object> requestParams = new TreeMap<String, Object>();
        Enumeration paramNames = request.getParameterNames();
        while (paramNames.hasMoreElements()) {
            String name = paramNames.nextElement().toString();
            requestParams.put(name, request.getParameter(name));
        }
        buffer.append("<tr><td width='12%' valign='top'><b>Parameters</b></td><td>");
        writeMap(requestParams, buffer);
        buffer.append("</td></tr>");

        buffer.append("<tr><td width='12%'><b>Remote User</b></td><td>");
        buffer.append(request.getRemoteUser());
        buffer.append("</td></tr>");

        buffer.append("<tr><td width='12%' valign='top'><b>URI</b></td><td>");
        buffer.append("<a href='");
        String requestURI = ClickUtils.getRequestURI(request);
        buffer.append(requestURI);
        buffer.append("'>");
        buffer.append(requestURI);
        buffer.append("</a>");
        buffer.append("</td></tr>");

        buffer.append("<tr><td><b width='12%'>URL</b></td><td>");
        buffer.append("<a href='");
        buffer.append(request.getRequestURL());
        buffer.append("'>");
        buffer.append(request.getRequestURL());
        buffer.append("</a>");
        buffer.append("</td></tr>");

        Map<String, Object> sessionAttributes = new TreeMap<String, Object>();
        if (request.getSession(false) != null) {
            HttpSession session = request.getSession();
            attributeNames = session.getAttributeNames();
            while (attributeNames.hasMoreElements()) {
                String name = attributeNames.nextElement().toString();
                sessionAttributes.put(name, session.getAttribute(name));
            }
        }
        buffer.append("<tr><td width='12%' valign='top'><b>Session</b></td><td>");
        writeMap(sessionAttributes, buffer);
        buffer.append("</td></tr>");
        buffer.append("</table>\n");

        buffer.append("</div>\n");

        return buffer.toString();
    }

    // Protected Methods ------------------------------------------------------

    /**
     * Return the cause of the error.
     *
     * @return the cause of the error
     */
    protected Throwable getCause() {
        Throwable cause = null;
        if (error instanceof ServletException) {
            cause = ((ServletException) error).getRootCause();
            if (cause == null) {
                cause = error.getCause();
            }
        } else {
            cause = error.getCause();
        }
        if (cause != null && cause.getCause() != null) {
            cause = cause.getCause();
        }
        if (cause == null) {
            cause = error;
        }
        return cause;
    }

    /**
     * Return the error source column number, or -1 if not determined.
     *
     * @return the error source column number, or -1 if not determined
     */
    protected int getColumnNumber() {
        return columnNumber;
    }

    /**
     * Return true if the error was a Velocity parsing exception.
     *
     * @return true if the error was a Velocity parsing exception
     */
    protected boolean isParseError() {
        return isParseError;
    }

    /**
     * Return true if the application is in "production" mode.
     *
     * @return true if the application is in "production" mode
     */
    protected boolean isProductionMode() {
        return isProductionMode;
    }

    /**
     * Return the error source line number, or -1 if not determined.
     *
     * @return the error source line number, or -1 if not determined
     */
    protected int getLineNumber() {
        return lineNumber;
    }

    /**
     * Return the error message.
     *
     * @return the error message
     */
    protected String getMessage() {
        if (isParseError()) {
            String message = error.getMessage();

            String parseMsg = message;
            int startIndex = message.indexOf('\n');
            int endIndex = message.lastIndexOf("...");

            if (startIndex != -1 && endIndex > startIndex) {
                parseMsg = message.substring(startIndex + 1, endIndex);
            }

            parseMsg = ClickUtils.escapeHtml(parseMsg);

            parseMsg = StringUtils.replace(parseMsg, "...", ", &#160;");

            return parseMsg;

        } else {
            Throwable cause = getCause();

            String value = (cause.getMessage() != null) ? cause.getMessage() : "null";

            return ClickUtils.escapeHtml(value);
        }
    }

    /**
     * Return the error source name.
     *
     * @return the error source name
     */
    protected String getSourceName() {
        return sourceName;
    }

    /**
     * Return a LineNumberReader for the error source file, or null if not
     * defined.
     *
     * @return LineNumberReader for the error source file, or null if not
     *      defined
     */
    protected LineNumberReader getSourceReader() {
        return sourceReader;
    }

    /**
     * Return Java Source LineNumberReader for the given filename, or null if
     * not found.
     *
     * @param filename the name of the Java source file, e.g. /examples/Page.java
     * @return LineNumberReader for the given source filename, or null if
     *      not found
     * @throws FileNotFoundException if file could not be found
     */
    protected LineNumberReader getJavaSourceReader(String filename) throws FileNotFoundException {

        if (pageClass == null) {
            return null;
        }

        // Look for source file on classpath
        InputStream is = pageClass.getResourceAsStream(filename);
        if (is != null) {
            return new LineNumberReader(new InputStreamReader(is));
        }

        // Else search for source file under WEB-INF
        String rootPath = servletContext.getRealPath("/");

        String webInfPath = rootPath + File.separator + "WEB-INF";

        File sourceFile = null;

        File webInfDir = new File(webInfPath);
        if (webInfDir.isDirectory() && webInfDir.canRead()) {
            File[] dirList = webInfDir.listFiles();
            if (dirList != null) {
                for (File file : dirList) {
                    if (file.isDirectory() && file.canRead()) {
                        String sourcePath = file.toString() + filename;
                        sourceFile = new File(sourcePath);
                        if (sourceFile.isFile() && sourceFile.canRead()) {

                            FileInputStream fis = new FileInputStream(sourceFile);
                            return new LineNumberReader(new InputStreamReader(fis));
                        }
                    }
                }
            }
        }

        return null;
    }

    /**
     * Return a HTML rendered section of the source error with the error
     * line highlighted.
     *
     * @return a HTML rendered section of the parsing error page template
     */
    protected String getRenderedSource() {

        HtmlStringBuffer buffer = new HtmlStringBuffer(5 * 1024);

        buffer.append("<pre style='font-family: Courier New, courier;'>");

        if (sourceReader == null) {
            buffer.append("Source is not available.</span>");
            return buffer.toString();
        }

        final String errorLineStyle = "style='background-color:yellow;'";

        final String errorCharSpan = "<span style='color:red;text-decoration:underline;font-weight:bold;'>";

        try {
            String line = sourceReader.readLine();

            while (line != null) {
                if (skipLine()) {
                    line = sourceReader.readLine();
                    continue;
                }

                boolean isErrorLine = sourceReader.getLineNumber() == lineNumber;

                // Start div tag
                buffer.append("<div ");
                if (isErrorLine) {
                    buffer.append(errorLineStyle);
                }
                buffer.append(">");

                // Write out line number
                String lineStr = "" + sourceReader.getLineNumber();
                int numberSpace = 3 - lineStr.length();
                for (int i = 0; i < numberSpace; i++) {
                    buffer.append(" ");
                }
                if (isErrorLine) {
                    buffer.append("<b>");
                }
                buffer.append(sourceReader.getLineNumber());
                if (isErrorLine) {
                    buffer.append("</b>");
                }
                buffer.append(":  ");

                // Write out line content
                if (isErrorLine) {
                    if (isParseError()) {
                        HtmlStringBuffer htmlLine = new HtmlStringBuffer(line.length() * 2);

                        for (int i = 0; i < line.length(); i++) {
                            if (i == getColumnNumber() - 1) {
                                htmlLine.append(errorCharSpan);
                                htmlLine.append(line.charAt(i));
                                htmlLine.append("</span>");
                            } else {
                                htmlLine.append(line.charAt(i));
                            }
                        }
                        buffer.append(htmlLine.toString());

                    } else {
                        buffer.append(getRenderJavaLine(line));
                    }

                } else {
                    if (isParseError()) {
                        buffer.append(ClickUtils.escapeHtml(line));
                    } else {
                        buffer.append(getRenderJavaLine(line));
                    }
                }

                // Close div tag
                buffer.append("</div>");

                line = sourceReader.readLine();
            }

        } catch (IOException ioe) {
            buffer.append("Could not load page source: ");
            buffer.append(ClickUtils.escapeHtml(ioe.toString()));
        } finally {
            try {
                sourceReader.close();
            } catch (IOException ioe) {
                // do nothing
            }
        }

        buffer.append("</pre>");

        return buffer.toString();
    }

    /**
     * Return a HTML encode stack trace string from the given error.
     *
     * @return a HTML encode stack trace string.
     */
    protected String getStackTrace() {

        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        getCause().printStackTrace(pw);

        HtmlStringBuffer buffer = new HtmlStringBuffer(sw.toString().length() + 80);
        buffer.append("<pre><tt style='font-size:10pt;'>");
        buffer.append(ClickUtils.escapeHtml(sw.toString().trim()));
        buffer.append("</tt></pre>");

        return buffer.toString();
    }

    /**
     * Write out the map name value pairs as name=value lines to the string
     * buffer.
     *
     * @param map the Map of name value pairs
     * @param buffer the string buffer to write out the values to
     */
    protected void writeMap(Map<String, Object> map, HtmlStringBuffer buffer) {
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();

            buffer.append(key);
            buffer.append("=");
            if (value != null) {
                buffer.append(ClickUtils.escapeHtml(value.toString()));
            } else {
                buffer.append("null");
            }
            buffer.append("<br/>");
        }
        if (map.isEmpty()) {
            buffer.append("&#160;");
        }
    }

    /**
     * Return a HTML rendered Java source line with keywords highlighted
     * using the given line.
     *
     * @param line the Java source line to render
     * @return HTML rendered Java source line
     */
    protected String getRenderJavaLine(String line) {
        line = ClickUtils.escapeHtml(line);

        for (String keyword : JAVA_KEYWORDS) {
            line = renderJavaKeywords(line, keyword);
        }

        return line;
    }

    /**
     * Render the HTML rendered Java source line with the given keyword
     * highlighted.
     *
     * @param line the given Java source line to render
     * @param keyword the Java keyword to highlight
     * @return the HTML rendered Java source line with the given keyword
     *      highlighted
     */
    protected String renderJavaKeywords(String line, String keyword) {
        String markupToken = "<span style='color:#7f0055;font-weight:bold;'>" + keyword + "</span>";

        line = StringUtils.replace(line, " " + keyword + " ", " " + markupToken + " ");

        if (line.startsWith(keyword)) {
            line = markupToken + line.substring(keyword.length());
        }

        if (line.endsWith(keyword)) {
            line = line.substring(0, line.length() - keyword.length()) + markupToken;
        }

        return line;
    }

    /**
     * Return true if the current line read from the source line reader, should
     * be skipped, or false if the current line should be rendered.
     *
     * @return true if the current line from the source reader should be skipped
     */
    protected boolean skipLine() {
        final int currentLine = getSourceReader().getLineNumber();
        final int errorLine = getLineNumber();

        if (Math.abs(currentLine - errorLine) <= 10) {
            return false;
        } else {
            return true;
        }
    }
}