Package com.google.visualization.datasource

Source Code of com.google.visualization.datasource.DataSourceHelper

// Copyright 2009 Google Inc.
//
// Licensed 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 com.google.visualization.datasource;

import com.google.visualization.datasource.base.DataSourceException;
import com.google.visualization.datasource.base.DataSourceParameters;
import com.google.visualization.datasource.base.InvalidQueryException;
import com.google.visualization.datasource.base.LocaleUtil;
import com.google.visualization.datasource.base.MessagesEnum;
import com.google.visualization.datasource.base.OutputType;
import com.google.visualization.datasource.base.ReasonType;
import com.google.visualization.datasource.base.ResponseStatus;
import com.google.visualization.datasource.base.StatusType;
import com.google.visualization.datasource.datatable.DataTable;
import com.google.visualization.datasource.query.AggregationColumn;
import com.google.visualization.datasource.query.Query;
import com.google.visualization.datasource.query.ScalarFunctionColumn;
import com.google.visualization.datasource.query.engine.QueryEngine;
import com.google.visualization.datasource.query.parser.QueryBuilder;
import com.google.visualization.datasource.render.CsvRenderer;
import com.google.visualization.datasource.render.HtmlRenderer;
import com.google.visualization.datasource.render.JsonRenderer;

import com.ibm.icu.util.ULocale;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.util.Locale;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* A Helper class providing convenience functions for serving data source requests.
*
* The class enables replying to a data source request with a single method which encompasses
* all the request processing - <code>executeDataSourceServletFlow</code>.
* To enable users to change the default flow all the basic operations (such as: query parsing,
* data table creation, query execution, and response creation) are also exposed.
*
* @author Yaniv S.
*/
public class DataSourceHelper {

  /**
   * The log used throughout the data source library.
   */
  private static final Log log = LogFactory.getLog(DataSourceHelper.class.getName());

  /**
   * The name of the http request parameter that indicates the requested locale.
   */
  /* package */ static final String LOCALE_REQUEST_PARAMETER = "hl";

  /**
   * A private constructor for this Singleton.
   */
  private DataSourceHelper() {}

  /**
   * Executes the default data source servlet flow.
   * Assumes restricted access mode.
   * @see <code>executeDataSourceServletFlow(HttpServletRequest req, HttpServletResponse resp,
   *     DataTableGenerator dtGenerator, boolean isRestrictedAccessMode)</code>
   *
   * @param req The HttpServletRequest.
   * @param resp The HttpServletResponse.
   * @param dtGenerator An implementation of {@link DataTableGenerator} interface.
   *
   * @throws IOException In case of I/O errors.
   */
  public static void executeDataSourceServletFlow(HttpServletRequest req, HttpServletResponse resp,
      DataTableGenerator dtGenerator) throws IOException {
    executeDataSourceServletFlow(req, resp, dtGenerator, true);
  }

  /**
   * Executes the default data source servlet flow.
   *
   * The default flow is as follows:
   * - Parse the request parameters.
   * - Verify access is approved (for restricted access mode only).
   * - Split the query.
   * - Generate the data-table using the data-table generator.
   * - Run the completion query.
   * - Set the servlet response.
   *
   * Usage note : this function executes the same flow provided to Servlets that inherit
   * <code>DataSourceServlet</code>.
   * Use this function when the default flow is required but <code>DataSourceServlet</code>
   * cannot be inherited (e.g., your servlet already inherits from anther class, or not in a
   * servlet context).
   *
   * @param req The HttpServletRequest.
   * @param resp The HttpServletResponse.
   * @param dtGenerator An implementation of {@link DataTableGenerator} interface.
   * @param isRestrictedAccessMode Indicates whether the server should serve trusted domains only.
   *     Currently this translates to serving only requests from the same domain.
   *
   * @throws IOException In case of I/O errors.
   */
  public static void executeDataSourceServletFlow(HttpServletRequest req, HttpServletResponse resp,
      DataTableGenerator dtGenerator, boolean isRestrictedAccessMode) throws IOException {
    // Extract the data source request parameters.
    DataSourceRequest dsRequest = null;
    try {
      dsRequest = new DataSourceRequest(req);

      if (isRestrictedAccessMode) {
        // Verify that the request is approved for access.
        DataSourceHelper.verifyAccessApproved(dsRequest);
      }

      // Split the query.
      QueryPair query = DataSourceHelper.splitQuery(dsRequest.getQuery(),
          dtGenerator.getCapabilities());

      // Generate the data table.
      DataTable dataTable = dtGenerator.generateDataTable(query.getDataSourceQuery(), req);

      // Apply the completion query to the data table.
      DataTable newDataTable = DataSourceHelper.applyQuery(query.getCompletionQuery(), dataTable,
          dsRequest.getUserLocale());

      // Set the response.
      setServletResponse(newDataTable, dsRequest, resp);
    } catch (DataSourceException e) {
      if (dsRequest != null) {
        setServletErrorResponse(e, dsRequest, resp);
      } else {
        DataSourceHelper.setServletErrorResponse(e, req, resp);
      }
    } catch (RuntimeException e) {
      log.error("A runtime exception has occured", e);
      ResponseStatus status = new ResponseStatus(StatusType.ERROR, ReasonType.INTERNAL_ERROR,
          e.getMessage());
      if (dsRequest == null) {
        dsRequest = DataSourceRequest.getDefaultDataSourceRequest(req);
      }
      DataSourceHelper.setServletErrorResponse(status, dsRequest, resp);
    }
  }

  /**
   * Checks that the given request is sent from the same domain as that of the server.
   *
   * @param req The data source request.
   *
   * @throws DataSourceException If the access for this request is denied.
   */
  public static void verifyAccessApproved(DataSourceRequest req) throws DataSourceException {
    // The library requires the request to be same origin for JSON and JSONP.
    // Check for (!csv && !html && !tsv-excel) to make sure any output type
    // added in the future will be restricted to the same domain by default.
    OutputType outType = req.getDataSourceParameters().getOutputType();
    if (outType != OutputType.CSV && outType != OutputType.TSV_EXCEL
        && outType != OutputType.HTML && !req.isSameOrigin()) {
      throw new DataSourceException(ReasonType.ACCESS_DENIED,
          "Unauthorized request. Cross domain requests are not supported.");
    }
  }

  // -------------------------- Servlet helper methods --------------------------------------------

  /**
   * Sets the response on the <code>HttpServletResponse</code> by creating a response message
   * for the given <code>DataTable</code> and sets it on the <code>HttpServletResponse</code>.
   *
   * @param dataTable The data table.
   * @param dataSourceRequest The data source request.
   * @param res The http servlet response.
   *
   * @throws IOException In case an error happened trying to write the response to the servlet.
   */
  public static void setServletResponse(DataTable dataTable, DataSourceRequest dataSourceRequest,
      HttpServletResponse res) throws IOException {
    String responseMessage = generateResponse(dataTable, dataSourceRequest);
    setServletResponse(responseMessage, dataSourceRequest, res);
  }

  /**
   * Sets the given response string on the <code>HttpServletResponse</code>.
   *
   * @param responseMessage The response message.
   * @param dataSourceRequest The data source request.
   * @param res The HTTP response.
   *
   * @throws IOException In case an error happened trying to write to the servlet response.
   */
  public static void setServletResponse(String responseMessage,
      DataSourceRequest dataSourceRequest, HttpServletResponse res) throws IOException {
    DataSourceParameters dataSourceParameters = dataSourceRequest.getDataSourceParameters();
    ResponseWriter.setServletResponse(responseMessage, dataSourceParameters, res);
  }


  /**
   * Sets the HTTP servlet response in case of an error.
   *
   * @param dataSourceException The data source exception.
   * @param dataSourceRequest The data source request.
   * @param res The http servlet response.
   *
   * @throws IOException In case an error happened trying to write the response to the servlet.
   */
  public static void setServletErrorResponse(DataSourceException dataSourceException,
      DataSourceRequest dataSourceRequest, HttpServletResponse res) throws IOException {
    String responseMessage = generateErrorResponse(dataSourceException, dataSourceRequest);
    setServletResponse(responseMessage, dataSourceRequest, res);
  }

  /**
   * Sets the HTTP servlet response in case of an error.
   *
   * @param responseStatus The response status.
   * @param dataSourceRequest The data source request.
   * @param res The http servlet response.
   *
   * @throws IOException In case an error happened trying to write the response to the servlet.
   */
  public static void setServletErrorResponse(ResponseStatus responseStatus,
      DataSourceRequest dataSourceRequest, HttpServletResponse res) throws IOException {
    String responseMessage = generateErrorResponse(responseStatus, dataSourceRequest);
    setServletResponse(responseMessage, dataSourceRequest, res);
  }

  /**
   * Sets the HTTP servlet response in case of an error.
   *
   * Gets an <code>HttpRequest</code> parameter instead of a <code>DataSourceRequest</code>.
   * Use this when <code>DataSourceRequest</code> is not available, for example, if
   * <code>DataSourceRequest</code> constructor failed.
   *
   * @param dataSourceException The data source exception.
   * @param req The http servlet request.
   * @param res The http servlet response.
   *
   * @throws IOException In case an error happened trying to write the response to the servlet.
   */
  public static void setServletErrorResponse(DataSourceException dataSourceException,
      HttpServletRequest req, HttpServletResponse res) throws IOException {
    DataSourceRequest dataSourceRequest = DataSourceRequest.getDefaultDataSourceRequest(req);
    setServletErrorResponse(dataSourceException, dataSourceRequest, res);
  }

  // -------------------- Response message helper methods. ----------------------------------------

  /**
   * Generates a string response for the given <code>DataTable</code>.
   *
   * @param dataTable The data table.
   * @param dataSourceRequest The data source request.
   *
   * @return The response string.
   */
  public static String generateResponse(DataTable dataTable, DataSourceRequest dataSourceRequest) {
    CharSequence response;
    ResponseStatus responseStatus = null;
    if (!dataTable.getWarnings().isEmpty()) {
      responseStatus = new ResponseStatus(StatusType.WARNING);
    }
    switch (dataSourceRequest.getDataSourceParameters().getOutputType()) {
      case CSV:
        response = CsvRenderer.renderDataTable(dataTable, dataSourceRequest.getUserLocale(), ",");
        break;
      case TSV_EXCEL:
        response = CsvRenderer.renderDataTable(dataTable, dataSourceRequest.getUserLocale(), "\t");
        break;
      case HTML:
        response = HtmlRenderer.renderDataTable(dataTable, dataSourceRequest.getUserLocale());
        break;
      case JSONP:
        // Appending a comment to the response to prevent the first characters to be the
        // response handler which is not controlled by the server.
        response = "// Data table response\n" + JsonRenderer.renderJsonResponse(
            dataSourceRequest.getDataSourceParameters(), responseStatus, dataTable);
        break;
      case JSON:
        response = JsonRenderer.renderJsonResponse(
            dataSourceRequest.getDataSourceParameters(), responseStatus, dataTable);
        break;
      default:
        // This should never happen.
        throw new RuntimeException("Unhandled output type.");
    }

    return response.toString();
  }

  /**
   * Generates an error response string for the given {@link DataSourceException}.
   * Receives an exception, and renders it to an error response according to the
   *{@link OutputType} specified in the {@link DataSourceRequest}.
   *
   * Note: modifies the response status to make links clickable in cases where the reason type is
   * {@link ReasonType#USER_NOT_AUTHENTICATED}. If this is not required call generateErrorResponse
   * directly with a {@link ResponseStatus}.
   *
   * @param dse The data source exception.
   * @param dsRequest The DataSourceRequest.
   *
   * @return The error response string.
   *
   * @throws IOException In case if I/O errors.
   */
  public static String generateErrorResponse(DataSourceException dse, DataSourceRequest dsRequest)
      throws IOException {
    ResponseStatus responseStatus = ResponseStatus.createResponseStatus(dse);
    responseStatus = ResponseStatus.getModifiedResponseStatus(responseStatus);
    return generateErrorResponse(responseStatus, dsRequest);
  }

  /**
   * Generates an error response string for the given <code>ResponseStatus</code>.
   * Render the <code>ResponseStatus</code> to an error response according to the
   * <code>OutputType</code> specified in the <code>DataSourceRequest</code>.
   *
   * @param responseStatus The response status.
   * @param dsRequest The DataSourceRequest.
   *
   * @return The error response string.
   *
   * @throws IOException In case if I/O errors.
   */
  public static String generateErrorResponse(ResponseStatus responseStatus,
      DataSourceRequest dsRequest) throws IOException {
    DataSourceParameters dsParameters = dsRequest.getDataSourceParameters();
    CharSequence response;
    switch (dsParameters.getOutputType()) {
      case CSV:
      case TSV_EXCEL:
        response = CsvRenderer.renderCsvError(responseStatus);
        break;
      case HTML:
        response = HtmlRenderer.renderHtmlError(responseStatus);
        break;
      case JSONP:
        response = JsonRenderer.renderJsonResponse(dsParameters, responseStatus, null);
        break;
      case JSON:
        response = JsonRenderer.renderJsonResponse(dsParameters, responseStatus, null);
        break;
      default:
        // This should never happen.
        throw new RuntimeException("Unhandled output type.");
    }
    return response.toString();
  }

  // -------------------------- Query helper methods ----------------------------------------------

  /** @see #parseQuery(String, ULocale)*/
  public static Query parseQuery(String queryString) throws InvalidQueryException {
    return parseQuery(queryString, null);
  }
 
  /**
   * Parses a query string (e.g., 'select A,B pivot B') and creates a Query object.
   * Throws an exception if the query is invalid.
   *
   * @param queryString The query string.
   * @param locale The user locale.
   *
   * @return The parsed query object.
   *
   * @throws InvalidQueryException If the query is invalid.
   */
  public static Query parseQuery(String queryString, ULocale userLocale)
      throws InvalidQueryException {
    QueryBuilder queryBuilder = QueryBuilder.getInstance();
    Query query = queryBuilder.parseQuery(queryString, userLocale);

    return query;
  }

  /**
   * Applies the given <code>Query</code> on the given <code>DataTable</code> and returns the
   * resulting <code>DataTable</code>. This method may change the given DataTable.
   * Error messages produced by this method will be localized according to the passed locale
   * unless the specified {@code DataTable} has a non null locale.
   *
   * @param query The query object.
   * @param dataTable The data table on which to apply the query.
   * @param locale The user locale for the current request.
   *
   * @return The data table result of the query execution over the given data table.
   *
   * @throws InvalidQueryException If the query is invalid.
   * @throws DataSourceException If the data source cannot execute the query.
   */
  public static DataTable applyQuery(Query query, DataTable dataTable, ULocale locale)
      throws InvalidQueryException, DataSourceException {
    dataTable.setLocaleForUserMessages(locale);
    validateQueryAgainstColumnStructure(query, dataTable);
    dataTable = QueryEngine.executeQuery(query, dataTable, locale);
    dataTable.setLocaleForUserMessages(locale);
    return dataTable;
  }

  /**
   * Splits the <code>Query</code> object into two queries according to the declared data source
   * capabilities: data source query and completion query.
   *
   * The data source query is executed first by the data source itself. Afterward, the
   * <code>QueryEngine</code> executes the completion query over the resulting data table.
   *
   * @param query The query to split.
   * @param capabilities The declared capabilities of the data source.
   *
   * @return A QueryPair object.
   *
   * @throws DataSourceException If the query cannot be split.
   */
  public static QueryPair splitQuery(Query query, Capabilities capabilities)
      throws DataSourceException {
    return QuerySplitter.splitQuery(query, capabilities);
  }

  /**
   * Checks that the query is valid against the structure of the data table.
   * A query is invalid if:
   * <ol>
   * <li> The query references column ids that don't exist in the data table.
   * <li> The query contains calculated columns operations (i.e., scalar function, aggregations)
   * that do not match the relevant columns type.
   * </ol>
   *
   * Note: does NOT validate the query itself, i.e. errors like "SELECT a, a" or
   * "SELECT a GROUP BY a" will not be caught. These kind of errors should be checked elsewhere
   * (preferably by the <code>Query.validate()</code> method).
   *
   * @param query The query to check for validity.
   * @param dataTable The data table against which to validate. Only the columns are used.
   *
   * @throws InvalidQueryException Thrown if the query is found to be invalid
   *     against the given data table.
   */
  public static void validateQueryAgainstColumnStructure(Query query, DataTable dataTable)
      throws InvalidQueryException {
    // Check that all the simple columns exist in the table (including the
    // simple columns inside aggregation and scalar-function columns)
    Set<String> mentionedColumnIds = query.getAllColumnIds();
    for (String columnId : mentionedColumnIds) {
      if (!dataTable.containsColumn(columnId)) {
        String messageToLogAndUser = MessagesEnum.NO_COLUMN.getMessageWithArgs(
            dataTable.getLocaleForUserMessages(), columnId);
        log.error(messageToLogAndUser);
        throw new InvalidQueryException(messageToLogAndUser);
      }
    }

    // Check that all aggregation columns are valid (i.e., the aggregation type
    // matches the columns type).
    Set<AggregationColumn> mentionedAggregations = query.getAllAggregations();
    for (AggregationColumn agg : mentionedAggregations) {
      try {
        agg.validateColumn(dataTable);
      } catch (RuntimeException e) {
        log.error("A runtime exception has occured", e);
        throw new InvalidQueryException(e.getMessage());
      }
    }

    // Check that all scalar function columns are valid. (i.e., the scalar
    // function matches the columns types).
    Set<ScalarFunctionColumn> mentionedScalarFunctionColumns =
        query.getAllScalarFunctionsColumns();
    for (ScalarFunctionColumn col : mentionedScalarFunctionColumns) {
      col.validateColumn(dataTable);
    }
  }

  /**
   * Get the locale from the given request.
   *
   * @param req The http serlvet request
   *
   * @return The locale for the given request.
   */
  public static ULocale getLocaleFromRequest(HttpServletRequest req) {
    Locale locale;
    String requestLocale = req.getParameter(LOCALE_REQUEST_PARAMETER);
    if (requestLocale != null) {
      // Try to take the locale from the 'hl' parameter in the request.
      locale = LocaleUtil.getLocaleFromLocaleString(requestLocale);
    } else {
      // Else, take the browser locale.
      locale = req.getLocale();
    }
    return ULocale.forLocale(locale);
  }
}
TOP

Related Classes of com.google.visualization.datasource.DataSourceHelper

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.