Package org.gomba

Source Code of org.gomba.SingleQueryServlet

package org.gomba;

import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* Base class for servlets that perform a SELECT and render the results to the
* HTTP response body.
*
* </p>
* Init params:
* <dl>
* <dt>query</dt>
* <dd>The SQL query to execute. May contain ${} parameters. This init-param
* also accepts a path to a dynamic resource (a JSP) when dynamic SQL generation
* is needed. The path must begin with a "/" and is interpreted as relative to
* the current context root. (Required)</dd>
* <dt>skip</dt>
* <dd>The number of records to skip. May contain ${} parameters. (Optional)
* </dd>
* <dt>max</dt>
* <dd>The maximum number of records to load. May contain ${} parameters.
* (Optional)</dd>
* <dt>nodata-http-status</dt>
* <dd>The HTTP status code in case of empty resultset. If the code is 200 (OK)
* then the subclassing servlet will output its default value. Defaults to 200
* (OK). A useful code is 404 (Not found). (Optional)</dd>
* <dt>nodata-default-resource</dt>
* <dd>Path to a resource to serve in case of empty resultset. The path must
* begin with a "/" and is interpreted as relative to the current context root.
* When this init-param is not specified, the subclassing servlet default output
* is used. (Optional)</dd>
* </dl>
* </p>
*
* @author Flavio Tordini
* @version $Id: SingleQueryServlet.java,v 1.5 2005/10/19 13:48:16 flaviotordini Exp $
*/
public abstract class SingleQueryServlet extends AbstractServlet {

    private final static String INIT_PARAM_QUERY = "query";

    private final static String INIT_PARAM_SKIP = "skip";

    private final static String INIT_PARAM_MAX = "max";

    private final static String INIT_PARAM_NO_DATA_HTTP_STATUS = "nodata-http-status";

    private final static String INIT_PARAM_NO_DATA_DEFAULT_RESOURCE = "nodata-default-resource";

    /**
     * The parsed query definition. It is null when the query is dynamic, i.e. a
     * dynamic resource (a JSP) is used to generate the SQL.
     */
    private QueryDefinition queryDefinition;

    /**
     * The path of a resource that dynamically generates a SQL query.
     */
    private String queryResource;

    /**
     * Skip and max expressions. These are only set when a dynamic query is
     * used.
     */
    private Expression skip, max;

    /**
     * The HTTP status code in case of empty resultset.
     */
    private int noDataHttpStatusCode = HttpServletResponse.SC_OK;

    /**
     * Path to a resource to serve in case of empty resultset. If null the
     * doDefaultOutput method is invoked.
     */
    private String noDataDefaultResource;

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

        // parse the query definition
        try {
            String query = config.getInitParameter(INIT_PARAM_QUERY);
            String skip = config.getInitParameter(INIT_PARAM_SKIP);
            String max = config.getInitParameter(INIT_PARAM_MAX);
            if (!query.startsWith("/")) {
                this.queryDefinition = new QueryDefinition(query, skip, max);
            } else {
                this.queryResource = query;
                if (skip != null) {
                    this.skip = new Expression(skip);
                }
                if (max != null) {
                    this.max = new Expression(max);
                }
            }
        } catch (Exception e) {
            throw new ServletException("Error parsing query definition.", e);
        }

        // the HTTP status code to send in case of empty resultset.
        try {
            String noDataHttpStatusCodeString = config
                    .getInitParameter(INIT_PARAM_NO_DATA_HTTP_STATUS);
            if (noDataHttpStatusCodeString != null) {
                this.noDataHttpStatusCode = Integer
                        .parseInt(noDataHttpStatusCodeString);
            }
        } catch (NumberFormatException e) {
            throw new ServletException("Error parsing "
                    + INIT_PARAM_NO_DATA_HTTP_STATUS, e);
        }

        // default resource in case of empty resultset.
        this.noDataDefaultResource = config
                .getInitParameter(INIT_PARAM_NO_DATA_DEFAULT_RESOURCE);
    }

    /**
     * Get the QueryDefinition, it can be a fixed QueryDefinition created at
     * init-time. Or a dynamic one created by evaluating a JSP.
     */
    private QueryDefinition getQueryDefinition(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        QueryDefinition requestQueryDefinition;
        if (this.queryDefinition == null) {
            // dynamic query
            String sql = getDynamicQuery(this.queryResource, request, response);
            try {
                requestQueryDefinition = new QueryDefinition(sql, this.skip,
                        this.max);
            } catch (Exception e) {
                throw new ServletException("Error parsing query definition.", e);
            }
        } else {
            // fixed query
            requestQueryDefinition = this.queryDefinition;
        }
        return requestQueryDefinition;
    }

    /**
     * The real work is done here.
     *
     * @param request
     *                   The HTTP request
     * @param response
     *                   The HTTP response
     * @param renderBody
     *                   Wheter to render the response body or not.
     */
    protected final void processRequest(HttpServletRequest request,
            HttpServletResponse response, boolean renderBody)
            throws ServletException, IOException {

        // get current time for benchmarking purposes
        final long startTime = System.currentTimeMillis();

        // create the parameter resolver that will help us throughout this
        // request
        final ParameterResolver parameterResolver = new ParameterResolver(
                request);

        // get the query definition
        QueryDefinition requestQueryDefinition = getQueryDefinition(request,
                response);

        // build the Query
        final Query query = getQuery(requestQueryDefinition, parameterResolver);

        // find out if this request is part of a transaction
        Transaction transaction = getTransaction(parameterResolver);

        Query.QueryResult queryResult = null;
        Connection connection = null;

        // surround everything in this try/finally to be able to free JDBC
        // resources even in case of exceptions
        try {

            try {

                if (transaction == null) {
                    connection = getDataSource().getConnection();
                } else {
                    if (isDebugMode()) {
                        log("Request is part of transaction: "
                                + transaction.getUri());
                    }
                    connection = transaction.getConnection();
                }

                // execute the query
                queryResult = query.execute(connection);

                // queryResult may be null, if the query is an update.
                if (queryResult != null) {

                    // Make sure the resultset cursor is positioned on a row. If
                    // resultset is empty or after the last row, stop processing
                    // this request.
                    if (!maybeMoveCursor(queryResult.getResultSet())) {
                        // the resultset is empty!

                        // if status is not 200 set the HTTP status and stop
                        if (this.noDataHttpStatusCode != HttpServletResponse.SC_OK) {
                            response.sendError(this.noDataHttpStatusCode);
                            return;
                        }

                        // set the response headers right away
                        // if the response headers include an expression using
                        // the 'column' domain (which requires a resultset
                        // available to the ParameterResolver) an exception will
                        // be thrown. The exception is caught and logged if in
                        // debug mode.
                        Map responseHeaders = getResponseHeaders();
                        if (responseHeaders != null) {
                            try {
                                for (Iterator i = responseHeaders.entrySet()
                                        .iterator(); i.hasNext();) {
                                    Map.Entry mapEntry = (Map.Entry) i.next();

                                    String headerName = (String) mapEntry
                                            .getKey();
                                    Object headerValue;
                                    try {
                                        headerValue = ((Expression) mapEntry
                                                .getValue())
                                                .replaceParameters(parameterResolver);
                                    } catch (ParameterResolver.UnavailableResultSetException urse) {
                                        if (isDebugMode()) {
                                            log("Cannot set response header: "
                                                    + headerName, urse);
                                        }
                                        continue;
                                    }
                                    setResponseHeader(response, headerName,
                                            headerValue);

                                }
                            } catch (Exception e) {
                                throw new ServletException(
                                        "Error setting response headers.", e);
                            }
                        }

                        if (this.noDataDefaultResource == null) {
                            // default output
                            try {
                                // subclasses will implement this!
                                doDefaultOutput(response);
                            } catch (Exception e) {
                                throw new ServletException(
                                        "Error rendering default output.", e);
                            }
                        } else {
                            // default resource
                            serveDefaultResource(this.noDataDefaultResource,
                                    request, response);
                        }

                        // stop processing this request.
                        return;
                    }

                    // set reference to the result set in order to resolve
                    // 'column' domain parameters
                    parameterResolver.setResultSet(queryResult.getResultSet());

                }

            } catch (Exception e) {
                // log the SQL for debugging
                log("Error executing query: " + query, e);
                // but don't expose the SQL nor the exception on the web for
                // security reasons. We don't want users to be able to see our
                // SQL nor the JDBC driver exception messages which usually
                // contain table names and such.
                response.sendError(
                        HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                        "Error executing query. See logs for details.");
                return;
            }

            try {
                ResultSet resultSet = null;
                if (queryResult != null) {
                    resultSet = queryResult.getResultSet();
                }
                // subclasses will implement this!
                doInput(resultSet, request, parameterResolver, connection);
            } catch (Exception e) {
                throw new ServletException("Error processing request body.", e);
            }

            // set the response headers
            setResponseHeaders(response, parameterResolver);

            // set the HTTP status code
            if (getHttpStatusCode() != HttpServletResponse.SC_OK) {
                response.setStatus(getHttpStatusCode());
            }

            // optionally write to the response body
            if (renderBody) {
                if (queryResult == null) {
                    throw new ServletException(
                            "Resultset is null. The query didn't return a resultset.");
                }
                try {
                    // subclasses will implement this!
                    doOutput(queryResult.getResultSet(), response,
                            parameterResolver);
                } catch (Exception e) {
                    throw new ServletException("Error rendering results.", e);
                }
            }

        } finally {
            // *always* free the JDBC resources!
            try {
                if (queryResult != null) {
                    try {
                        queryResult.close();
                    } catch (Exception e) {
                        throw new ServletException(
                                "Error freeing JDBC resources.", e);
                    }
                }

            } finally {
                // close the JDBC connection if this request is not part of a
                // transaction
                if (transaction == null && connection != null) {
                    try {
                        connection.close();
                    } catch (Exception e) {
                        throw new ServletException(
                                "Error closing JDBC connection.", e);
                    }
                }
            }

            // processing time
            if (isDebugMode()) {
                log(getProfilingMessage(request, startTime));
            }
        }

    }

    /**
     * Override this method in order to process data from the request body. The
     * contract for subclasses is not to close the ResultSet and not to call
     * ResultSet.next().
     *
     * @param resultSet
     *                   The resultset, may be null.
     * @param request
     *                   The HTTP request to read from
     * @param parameterResolver
     *                   The object used to resolve parameters.
     */
    protected void doInput(ResultSet resultSet,
            HttpServletRequest request, ParameterResolver parameterResolver, Connection connection)
            throws Exception {
        // dummy
    }

    /**
     * Render the content of the resultset in the response body. The contract
     * for subclasses is not to close the ResultSet and expect it to be
     * positioned on the first row to render (This means ResultSet.next() should
     * be called <strong>after </strong> the first row has been rendered.
     *
     * @param resultSet
     *                   The resultset to render
     * @param response
     *                   The HTTP response to write to
     * @param parameterResolver
     *                   The object used to resolve parameters.
     */
    protected void doOutput(ResultSet resultSet,
            HttpServletResponse response, ParameterResolver parameterResolver)
            throws Exception {
        // dummy
    }

    /**
     * Render a default value when the resultset is empty (0 rows).
     *
     * @param response
     *                   The HTTP response to write to
     */
    protected void doDefaultOutput(HttpServletResponse response)
            throws Exception {
        // dummy
    }

    /**
     * Serve a default resource.
     *
     * @param defaultResource
     *                   Path to the resource
     * @param request
     *                   The HTTP request
     * @param response
     *                   The HTTP response
     */
    private final void serveDefaultResource(String defaultResource,
            HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // get the dispatcher
        RequestDispatcher dispatcher = getServletContext()
                .getRequestDispatcher(defaultResource);
        if (dispatcher == null) {
            throw new ServletException(
                    "Cannot get a RequestDispatcher for path: "
                            + defaultResource);
        }
        dispatcher.forward(request, response);
    }
}
TOP

Related Classes of org.gomba.SingleQueryServlet

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.