Package com.google.visualization.datasource.render

Source Code of com.google.visualization.datasource.render.JsonRenderer

// 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.render;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.StrBuilder;

import java.util.List;
import java.util.Map;

import com.google.common.collect.Lists;
import com.google.visualization.datasource.DataSourceRequest;
import com.google.visualization.datasource.base.DataSourceParameters;
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.base.Warning;
import com.google.visualization.datasource.datatable.ColumnDescription;
import com.google.visualization.datasource.datatable.DataTable;
import com.google.visualization.datasource.datatable.TableCell;
import com.google.visualization.datasource.datatable.TableRow;
import com.google.visualization.datasource.datatable.value.BooleanValue;
import com.google.visualization.datasource.datatable.value.DateTimeValue;
import com.google.visualization.datasource.datatable.value.DateValue;
import com.google.visualization.datasource.datatable.value.NumberValue;
import com.google.visualization.datasource.datatable.value.TimeOfDayValue;
import com.google.visualization.datasource.datatable.value.Value;
import com.google.visualization.datasource.datatable.value.ValueType;
import com.ibm.icu.util.GregorianCalendar;

/**
* Takes a data table and returns a json string.
* <p/>
* The renderer renders a response which primarily contains the serializing of a data table.
* The response type can be either json or jsonp (a json format wrapped inside a callback method).
* The main difference regarding data table serialization between the two types, is the way dates
* are serialized. Dates have no standard representation in json thus they are rendered according
* to the future use of the response. When the response type is jsonp, the data table is likely to
* be evaluated with standard browser 'eval' and therefore date represented by Date constructor
* will be transformed into javaScript dates (as expected), e.g new Date(2011,1,1).
* When the response type is json, Date constructors can not be used (not defined in the json
* standard) and so custom date strings are used instead. In this case, custom parsing of the string
* is required to construct dates objects from their respective strings, e.g "Date(2011,1,1)".
*
* @author Nimrod T.
*/
public class JsonRenderer extends AbstractRenderer {

    @Override
    public String getMimetype() {
        return "application/json";
    }

    /**
     * Returns a String-form 32-bit hash of this table's json.
     * Note: the signature ignores formatting.
     *
     * @param data The data table.
     * @return a String-form 64-bit hash of this table.
     */
    protected String getSignature(DataTable data) {
        String tableAsString = this.renderDataTable(data, true, false).toString();
        // Casting to long to avoid bug with abs(Integer.MIN_VALUE) being negative.
        long longHashCode = tableAsString.hashCode();
        return String.valueOf(Math.abs(longHashCode));
    }

    /**
     * Returns the part of the json string that describes a single warning or error.
     *
     * @param reasonType  The reason for the warning or error.
     * @param description The detailed description for this warning or error.
     * @return The part of the json string.
     */
    protected String getFaultString(ReasonType reasonType, String description) {
        List<String> objectParts = Lists.newArrayList();
        if(reasonType != null) {
            objectParts.add("\"reason\":\"" + reasonType.lowerCaseString() + "\"");
            objectParts.add("\"message\":\"" + EscapeUtil.jsonEscape(
                    reasonType.getMessageForReasonType(null)) + "\"");
        }
        if(description != null) {
            objectParts.add("\"detailed_message\":\"" + EscapeUtil.jsonEscape(description)
                    + "\"");
        }
        return new StrBuilder("{").appendWithSeparators(objectParts, ",").append("}").toString();
    }

    @Override
    public CharSequence error(final DataSourceRequest request, final ResponseStatus status) {
        StrBuilder sb = new StrBuilder();
        sb.append("{\"version\":\"0.6\"");

        final DataSourceParameters dsParams = request.getDataSourceParameters();
        // If no reqId found in the request, do not return reqId in the response.
        String requestId = dsParams.getRequestId();
        if(requestId != null) {
            sb.append(",\"reqId\":\"").append(EscapeUtil.jsonEscape(requestId)).append("\"");
        }
        // Check signature.
        String previousSignature = dsParams.getSignature();
        StatusType statusType = status.getStatusType();
        sb.append(",\"status\":\"").append(statusType.lowerCaseString()).append("\"");
        // Status is error.
        sb.append(",\"errors\":[");
        sb.append(getFaultString(status.getReasonType(), status.getDescription()));
        sb.append("]");
        sb.append("}");
        return sb.toString();
    }


    @Override
    public CharSequence render(final DataSourceRequest request, final DataTable data) {
        StrBuilder sb = new StrBuilder();
        sb.append("{\"version\":\"0.6\"");
        // If no reqId found in the request, do not return reqId in the response.
        String requestId = request.getDataSourceParameters().getRequestId();
        if(requestId != null) {
            sb.append(",\"reqId\":\"").append(EscapeUtil.jsonEscape(requestId)).append("\"");
        }
        ResponseStatus responseStatus = null;
        if(!data.getWarnings().isEmpty()) {
            responseStatus = new ResponseStatus(StatusType.WARNING);
        }
        if(responseStatus == null) {
            // Check signature.
            final String previousSignature = request.getDataSourceParameters().getSignature();
            if(!StringUtils.isEmpty(previousSignature) && (this.getSignature(data).equals(previousSignature))) {
                responseStatus = new ResponseStatus(StatusType.ERROR, ReasonType.NOT_MODIFIED, null);
            }
            else {
                responseStatus = new ResponseStatus(StatusType.OK, null, null);
            }
        }
        StatusType statusType = responseStatus.getStatusType();
        sb.append(",\"status\":\"").append(statusType.lowerCaseString()).append("\"");
        // There are reason and messages if the status is WARNING/ERROR.
        if(statusType != StatusType.OK) {
            // Status is warning or error.
            if(statusType == StatusType.WARNING) {
                List<Warning> warnings = data.getWarnings();
                List<String> warningJsonStrings = Lists.newArrayList();
                if(warnings != null) {
                    for(Warning warning : warnings) {
                        warningJsonStrings.add(getFaultString(warning.getReasonType(), warning.getMessage()));
                    }
                }
                sb.append(",\"warnings\":[").appendWithSeparators(warningJsonStrings, ",").append("]");

            }
            else { // Status is error.
                sb.append(",\"errors\":[");
                sb.append(getFaultString(responseStatus.getReasonType(), responseStatus.getDescription()));
                sb.append("]");
            }
        }

        if((statusType != StatusType.ERROR)) {
            // MessageType OK or WARNING,
            // so need to attach a data table (and a signature).
            sb.append(",\"sig\":\"").append(this.getSignature(data)).append("\"");
            sb.append(",\"table\":").append(this.renderDataTable(data, true, true));
        }
        sb.append("}");
        return sb.toString();
    }

    /**
     * Generates a JSON representation of the data table object.
     *
     * @param dataTable         Data
     * @param includeValues     False if the json should contain just meta-data and column descriptions
     *                          but without the data rows.
     * @param includeFormatting False if formatting information should be omitted from the
     *                          generated json.
     * @return The char sequence with the Json string.
     */
    protected CharSequence renderDataTable(final DataTable dataTable,
                                           boolean includeValues, boolean includeFormatting) {
        if(dataTable.getColumnDescriptions().isEmpty()) {
            return StringUtils.EMPTY;
        }
        List<ColumnDescription> columnDescriptions = dataTable.getColumnDescriptions();

        StringBuilder sb = new StringBuilder();
        sb.append("{");
        sb.append("\"cols\":["); // column descriptions.

        ColumnDescription col;
        for(int colId = 0; colId < columnDescriptions.size(); colId++) {
            col = columnDescriptions.get(colId);
            appendColumnDescriptionJson(col, sb);
            if(colId != (columnDescriptions.size() - 1)) {
                sb.append(",");
            }
        }
        sb.append("]"); // columns.

        if(includeValues) {
            sb.append(",\"rows\":[");
            List<TableCell> cells;
            TableCell cell;
            ColumnDescription columnDescription;

            List<TableRow> rows = dataTable.getRows();
            for(int rowId = 0; rowId < rows.size(); rowId++) {
                TableRow tableRow = rows.get(rowId);
                cells = tableRow.getCells();
                sb.append("{\"c\":[");
                for(int cellId = 0; cellId < cells.size(); cellId++) {
                    cell = cells.get(cellId);
                    if(cellId < (cells.size() - 1)) {
                        appendCellJson(cell, sb, includeFormatting, false);
                        sb.append(",");
                    }
                    else {
                        // Last column in the row.
                        appendCellJson(cell, sb, includeFormatting, true);
                    }
                }
                sb.append("]");

                // Row properties.
                String customPropertiesString = getPropertiesMapString(tableRow.getCustomProperties());
                if(customPropertiesString != null) {
                    sb.append(",\"p\":").append(customPropertiesString);
                }

                sb.append("}"); // cells.
                if((rows.size() - 1) > rowId) {
                    sb.append(",");
                }
            }

            sb.append("]"); // rows.
        }

        // Table properties.
        String customPropertiesString = getPropertiesMapString(dataTable.getCustomProperties());
        if(customPropertiesString != null) {
            sb.append(",\"p\":").append(customPropertiesString);
        }

        sb.append("}"); // table.
        return sb;
    }

    /**
     * Appends a Json representing a cell to the string buffer.
     *
     * @param cell              The cell to write Json for.
     * @param sb                The string buffer to append to.
     * @param includeFormatting Flase if formatting information should be omitted from the json.
     * @param isLastColumn      Is this the last column in the row.
     * @return The input string builder.
     */
    protected StringBuilder appendCellJson(TableCell cell,
                                           StringBuilder sb, boolean includeFormatting, boolean isLastColumn) {
        Value value = cell.getValue();
        ValueType type = cell.getType();
        StringBuilder valueJson = new StringBuilder();
        GregorianCalendar calendar;
        String escapedFormattedString = "";
        boolean isJsonNull = false;

        // Prepare a Json string representing the current value.
        DateValue dateValue;
        TimeOfDayValue timeOfDayValue;
        if((value == null) || (value.isNull())) {
            valueJson.append("null");
            isJsonNull = true;
        }
        else {
            switch(type) {
                case BOOLEAN:
                    valueJson.append(((BooleanValue) value).getValue());
                    break;
                case DATE:
                    valueJson.append("Date(");
                    dateValue = (DateValue) value;
                    valueJson.append(dateValue.getYear()).append(",");
                    valueJson.append(dateValue.getMonth()).append(",");
                    valueJson.append(dateValue.getDayOfMonth());
                    valueJson.append(")");
                    this.appendDate(valueJson);
                    break;
                case NUMBER:
                    valueJson.append(((NumberValue) value).getValue());
                    break;
                case TEXT:
                    valueJson.append("\"");
                    valueJson.append(EscapeUtil.jsonEscape(value.toString()));
                    valueJson.append("\"");
                    break;
                case TIMEOFDAY:
                    valueJson.append("[");
                    timeOfDayValue = (TimeOfDayValue) value;
                    valueJson.append(timeOfDayValue.getHours()).append(",");
                    valueJson.append(timeOfDayValue.getMinutes()).append(",");
                    valueJson.append(timeOfDayValue.getSeconds()).append(",");
                    valueJson.append(timeOfDayValue.getMilliseconds());
                    valueJson.append("]");
                    break;
                case DATETIME:
                    calendar = ((DateTimeValue) value).getCalendar();
                    valueJson.append("Date(");
                    valueJson.append(calendar.get(GregorianCalendar.YEAR)).append(",");
                    valueJson.append(calendar.get(GregorianCalendar.MONTH)).append(",");
                    valueJson.append(calendar.get(GregorianCalendar.DAY_OF_MONTH));
                    valueJson.append(",");
                    valueJson.append(calendar.get(GregorianCalendar.HOUR_OF_DAY));
                    valueJson.append(",");
                    valueJson.append(calendar.get(GregorianCalendar.MINUTE)).append(",");
                    valueJson.append(calendar.get(GregorianCalendar.SECOND));
                    valueJson.append(")");
                    this.appendDate(valueJson);
                    break;
                default:
                    throw new IllegalArgumentException("Illegal value Type " + type);
            }
        }

        // Prepare an escaped string representing the current formatted value.
        String formattedValue = cell.getFormattedValue();
        if((value != null) && !value.isNull() && (formattedValue != null)) {
            escapedFormattedString = EscapeUtil.jsonEscape(formattedValue);
            // Check for a value of type TEXT if the formatted value equals
            // its ordinary toString.
            if((type == ValueType.TEXT) && value.toString().equals(formattedValue)) {
                escapedFormattedString = "";
            }
        }

        // Add a Json for this cell. And,
        // 1) If the formatted value is empty drop it.
        // 2) If the value is null, and it is not the last column in the row drop the entire Json.
        if((isLastColumn) || (!isJsonNull)) {
            sb.append("{");
            // Value
            sb.append("\"v\":").append(valueJson);
            // Formatted value
            if((includeFormatting) && (!escapedFormattedString.equals(""))) {
                sb.append(",\"f\":\"").append(escapedFormattedString).append("\"");
            }
            String customPropertiesString = getPropertiesMapString(cell.getCustomProperties());
            if(customPropertiesString != null) {
                sb.append(",\"p\":").append(customPropertiesString);
            }
            sb.append("}");
        }
        return sb;
    }

    protected void appendDate(StringBuilder valueJson) {
        // Rendering date in string format, e.g "Date(2011,1,1)"
        valueJson.insert(0, "\"");
        valueJson.append("\"");
    }

    /**
     * Appends a Json representing a column description to the string buffer.
     *
     * @param col The column description to write Json for.
     * @param sb  The string builder to append to.
     * @return The input string builder.
     */
    protected StringBuilder appendColumnDescriptionJson(
            ColumnDescription col, StringBuilder sb) {
        sb.append("{");
        sb.append("\"id\":\"").append(EscapeUtil.jsonEscape(col.getId())).append("\",");
        sb.append("\"label\":\"").append(EscapeUtil.jsonEscape(col.getLabel())).append("\",");
        sb.append("\"type\":\"").append(col.getType().getTypeCodeLowerCase()).append("\",");
        sb.append("\"pattern\":\"").append(EscapeUtil.jsonEscape(col.getPattern())).append("\"");

        String customPropertiesString = getPropertiesMapString(col.getCustomProperties());
        if(customPropertiesString != null) {
            sb.append(",\"p\":").append(customPropertiesString);
        }

        sb.append("}");
        return sb;
    }

    /**
     * Makes a string from a properties map.
     *
     * @param propertiesMap The properties map.
     * @return A json string.
     */
    protected String getPropertiesMapString(Map<String, String> propertiesMap) {
        String customPropertiesString = null;
        if((propertiesMap != null) && (!propertiesMap.isEmpty())) {
            List<String> customPropertiesStrings = Lists.newArrayList();
            for(Map.Entry<String, String> entry : propertiesMap.entrySet()) {
                customPropertiesStrings.add("\""
                        + EscapeUtil.jsonEscape(entry.getKey()) + "\":\""
                        + EscapeUtil.jsonEscape(entry.getValue()) + "\"");
            }
            customPropertiesString = new StrBuilder("{")
                    .appendWithSeparators(customPropertiesStrings, ",").append("}").toString();
        }
        return customPropertiesString;
    }
}
TOP

Related Classes of com.google.visualization.datasource.render.JsonRenderer

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.