com.healthcit.analytics.utils.CAHopeDataSourceUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.healthcit.analytics.utils.CAHopeDataSourceUtils.java

Source

/*******************************************************************************
 * Copyright (c) 2013 HealthCare It, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the BSD 3-Clause license
 * which accompanies this distribution, and is available at
 * http://directory.fsf.org/wiki/License:BSD_3Clause
 * 
 * Contributors:
 *     HealthCare It, Inc - initial API and implementation
 ******************************************************************************/
package com.healthcit.analytics.utils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;

import com.google.visualization.datasource.datatable.ColumnDescription;
import com.google.visualization.datasource.datatable.value.ValueType;
import com.google.visualization.datasource.query.AbstractColumn;
import com.google.visualization.datasource.query.Query;
import com.google.visualization.datasource.query.QuerySelection;
import com.google.visualization.datasource.query.SimpleColumn;
import com.healthcit.analytics.dto.DataTableMapper;
import com.healthcit.analytics.exceptions.QueryInvalidException;

public class CAHopeDataSourceUtils {

    private static final String DEFAULT_COLUMN_PREFIX = "Column";
    private static final String SPLITTER = ",";
    private static final String TYPE = "type";
    private static final String NAME = "name";

    /**
       * Returns a list of required columns based on the query and the actual
       * columns.
       *
       * @param query The user selection query.
       * @param availableColumns The list of possible columns.
       *
       * @return A List of required columns for the requested data table.
       */
    public static List<ColumnDescription> getRequiredColumns(Query query, JSONObject resultSet,
            List<Map<String, Object>> columnDataArray) {

        ColumnDescription[] columns = generateColumnDescriptions(getColumns(query, resultSet, columnDataArray),
                columnDataArray);

        return Arrays.asList(columns);

    }

    /**
     * Generates a list of AbstractColumn columns associated with this query
     */
    private static List<AbstractColumn> getColumns(Query query, JSONObject resultSet,
            List<Map<String, Object>> columnDataArray) {

        QuerySelection querySelection = new QuerySelection();

        List<String> columnIds = new LinkedList<String>();

        // If no SELECT statement was associated with this query,
        // then generate a QuerySelection consisting of columns
        // Column1, Column2, Column3 etc.
        // based on the total possible number of columns 
        // associated with the query
        if (query.getSelection() == null) {
            // get the number of columns associated with the resultSet
            int numColumns = getNumberOfColumns(resultSet);

            // based on the number of columns, create a collection of column ids
            for (int index = 1; index <= numColumns; ++index) {

                String columnId = DEFAULT_COLUMN_PREFIX + index;

                columnIds.add(columnId);
            }
        }
        // Otherwise, the columns must come from a SELECT statement.
        // Since the order of the columns received on the server is not guaranteed
        // to match the original order of the columns (specified on the browser side);
        // we reset the QuerySelection with the correct ordering,
        // which has been ensured in the columnDataArray object.
        else {
            columnIds = Arrays.asList((String[]) getFullColumnNameList(columnDataArray));
        }

        // generate a new set of columns, and 
        // update the QuerySelection with the new set of columns
        for (String columnId : columnIds) {
            SimpleColumn column = new SimpleColumn(StringUtils.trim(columnId));

            querySelection.addColumn(column);
        }

        query.setSelection(querySelection);

        // return the columns associated with the query
        return query.getSelection().getColumns();
    }

    /**
     * Validates a Query
     */
    public static void validateQuery(Query query, HttpServletRequest request) throws QueryInvalidException {
        // if the user specified a SELECT statement,
        // then the request parameter "orderedColumnNames" is required
        if (query.getSelection() != null
                && StringUtils.isBlank(request.getParameter(Constants.ORDERED_COLUMN_NAMES))) {
            throw new QueryInvalidException(
                    "ERROR: The request parameter \"orderedColumnNames\" is required when you specify a SELECT in the query.");
        }

    }

    /**
     * Transforms a list of AbstractColumn objects into ColumnDescription entities
     */
    private static ColumnDescription[] generateColumnDescriptions(List<AbstractColumn> columns,
            List<Map<String, Object>> columnDataArray) {

        List<ColumnDescription> list = new LinkedList<ColumnDescription>();

        // get the full list of column data types
        ValueType[] dataTypes = getFullColumnDataTypeList(columnDataArray);

        for (int i = 0; i < columns.size(); ++i) {
            AbstractColumn column = columns.get(i);

            // get the column's data type
            //TODO: The datatypes for each column should be dynamically determined.
            //( Currently, the datatypes have been hardcoded as "text" fields,
            // and in some cases they have been hardcoded as "numeric" fields 
            // on the client side.)
            ValueType columnType = dataTypes[i];

            ColumnDescription columnDescription = new ColumnDescription(column.getId(), columnType,
                    StringUtils.capitalize(column.getId()));

            list.add(columnDescription);
        }

        return list.toArray(new ColumnDescription[list.size()]);
    }

    public static Object getJSONValue(JSONObject json, Object key) {
        if (json.containsKey(key))
            return json.get(key);
        else
            return null;
    }

    public static boolean isArrayOrCollection(Object object) {
        if (object == null)
            return false;

        return (object.getClass().isArray() || object instanceof Collection);
    }

    /**
     * Returns a JSONArray that contains some metadata about the columns.
     * Currrently includes information about the column name and data type.
     */
    public static List<Map<String, Object>> getColumnData(HttpServletRequest request) {
        // if there is no "orderedColumnNames" request parameter then return null
        if (StringUtils.isBlank(Constants.ORDERED_COLUMN_NAMES))
            return null;

        // else, set up a JSONArray containing metadata about the columns
        //      JSONArray columnDataArray = new JSONArray();
        ArrayList<Map<String, Object>> columnDataArray = new ArrayList<Map<String, Object>>();

        // get the full list of columns in the request
        String[] allColumns = request.getParameter(Constants.ORDERED_COLUMN_NAMES).split(SPLITTER);

        // get the list of numeric columns in the request
        String[] numericColumns = StringUtils.split(request.getParameter(Constants.NUMERIC_DATA_TYPE));

        // get the list of boolean columns in the request
        String[] booleanColumns = StringUtils.split(request.getParameter(Constants.BOOLEAN_DATA_TYPE));

        // get the list of date columns in the request
        String[] dateColumns = StringUtils.split(request.getParameter(Constants.DATE_DATA_TYPE));

        // associate column data types with each column
        for (String column : allColumns) {
            // get the column data type
            ValueType columnDataType = getColumnDataType(column, numericColumns, booleanColumns, dateColumns);

            // create a JSONObject representing metadata for this column
            //         JSONObject columnMetaData = new JSONObject();
            //         
            //         columnMetaData.put( NAME, column );
            //         
            //         columnMetaData.put( TYPE, columnDataType );
            //         
            //         columnDataArray.add( columnMetaData );
            Map<String, Object> columnMetaData = new HashMap<String, Object>();
            columnMetaData.put(NAME, column);
            columnMetaData.put(TYPE, columnDataType);
            columnDataArray.add(columnMetaData);
        }

        // return the JSON object
        return columnDataArray;
    }

    /**
     * Returns an ordered, full list of names from the column metadata generated by a user's query.
     * @param columnDataArray
     * @return
     */
    public static String[] getFullColumnNameList(List<Map<String, Object>> columnDataArray) {
        Object[] array = extractFromJSONColumnData(columnDataArray, NAME);

        String[] names = Arrays.copyOf(array, array.length, String[].class);

        return names;
    }

    /**
     *  Returns an ordered, full list of data types from the column metadata generated by a user's query.
     * @param columnDataArray
     * @return
     */
    public static ValueType[] getFullColumnDataTypeList(List<Map<String, Object>> columnDataArray) {
        Object[] array = extractFromJSONColumnData(columnDataArray, TYPE);

        ValueType[] types = Arrays.copyOf(array, array.length, ValueType[].class);

        return types;
    }

    /**
     * Returns a filtered list of values from the given JSON column metadata
     * which match the given key.
     * @param columnDataArray
     * @param key
     * @return
     */
    private static Object[] extractFromJSONColumnData(List<Map<String, Object>> columnDataArray, String key) {
        if (columnDataArray == null)
            return null;

        Object[] extractedData = new Object[columnDataArray.size()];

        for (int i = 0; i < columnDataArray.size(); ++i) {
            Map<String, Object> map = columnDataArray.get(i);

            extractedData[i] = map.get(key);
        }

        return extractedData;
    }

    private static int getNumberOfColumns(JSONObject resultSet) {

        int numColumns = 0;

        // get the resultSet rows
        JSONArray rows = DataTableMapper.getRows(resultSet);

        if (rows != null && !rows.isEmpty()) {

            // get the first row
            JSONObject row = (JSONObject) rows.get(0);

            // get the first row's key
            Object key = DataTableMapper.getKey(row);

            // get the first row's value
            Object value = DataTableMapper.getValue(row);

            // Add the number of columns associated with the key
            numColumns += DataTableMapper.getTotalNumberOfColumns(key);

            // Add the number of columns associated with the value
            numColumns += DataTableMapper.getTotalNumberOfColumns(value);
        }

        return numColumns;
    }

    private static ValueType getColumnDataType(String column, String[] numericColumns, String[] booleanColumns,
            String[] dateColumns) {
        // the column data type
        ValueType columnDataType = null;

        // if this column was included in the request parameters as a numeric then declare it as such
        if (ArrayUtils.contains(numericColumns, column))
            columnDataType = ValueType.NUMBER;

        // else, if this column was included in the request parameters as a boolean then declare it as such
        else if (ArrayUtils.contains(booleanColumns, column))
            columnDataType = ValueType.BOOLEAN;

        // else, if this column was included in the request parameters as a date then declare it as such
        else if (ArrayUtils.contains(dateColumns, column))
            columnDataType = ValueType.DATE;

        // else, declare it as a TEXT field (the default)
        else
            columnDataType = ValueType.TEXT;

        return columnDataType;
    }

}