org.getobjects.appserver.publisher.GoSimpleJSONRenderer.java Source code

Java tutorial

Introduction

Here is the source code for org.getobjects.appserver.publisher.GoSimpleJSONRenderer.java

Source

/*
  Copyright (C) 2007-2014 Helge Hess
    
  This file is part of Go.
    
  Go is free software; you can redistribute it and/or modify it under
  the terms of the GNU Lesser General Public License as published by the
  Free Software Foundation; either version 2, or (at your option) any
  later version.
    
  Go is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
  License for more details.
    
  You should have received a copy of the GNU Lesser General Public
  License along with Go; see the file COPYING.  If not, write to the
  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.
*/
package org.getobjects.appserver.publisher;

import java.sql.SQLException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.getobjects.appserver.core.WOContext;
import org.getobjects.appserver.core.WORequest;
import org.getobjects.appserver.core.WOResponse;
import org.getobjects.foundation.NSException;
import org.getobjects.foundation.NSKeyValueCoding;
import org.getobjects.foundation.NSObject;
import org.getobjects.foundation.UList;
import org.getobjects.foundation.UObject;
import org.getobjects.foundation.UString;
import org.getobjects.foundation.kvc.MissingPropertyException;

/**
 * GoSimpleJSONRenderer
 * <p>
 * This is a simple renderer which can render plist objects into JSON. It checks
 * whether the client accepts JSON requests by:<br>
 * a) checking the 'accept' HTTP header (must allow application/json)<br>
 * b) checking for a 'format' form parameter with value 'json'<br>
 * <p>
 * If you want to enforce rendering using JSON, wrap your object in a
 * GoJSONResult object.
 */
public class GoSimpleJSONRenderer extends NSObject implements IGoObjectRenderer {
    protected static final Log log = LogFactory.getLog("GoSimpleJSONRenderer");

    final DateFormat dateFormat;

    public GoSimpleJSONRenderer() {
        this.dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
        this.dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    }

    /* control rendering */

    public boolean isJSONRequest(final WORequest _rq) {
        if (_rq == null)
            return false;

        if (_rq.acceptsContentType("application/json", false /* no wildcard */))
            return true;

        final String fmt = _rq.stringFormValueForKey("format");
        if (fmt != null && ("json".equals(fmt) || "jsonp".equals(fmt)))
            return true;

        return false;
    }

    public boolean canRenderObjectInContext(Object _object, WOContext _ctx) {
        /* enforce JSON */
        if (_object instanceof GoJSONResult)
            return true;

        /* check whether the client accepts JSON */

        final WORequest rq = _ctx.request();
        if (rq == null) {
            log.warn("missing request in context: " + _ctx);
            return false;
        }

        if (!this.isJSONRequest(rq)) {
            if (log.isInfoEnabled())
                log.info("request accepts no JSON: " + rq);
            return false;
        }

        if (_object instanceof GoException) // 404, 401 and such. Deliver as HTTP.
            return false;

        /* Note: do *NOT* move up, first we need to check whether the client
         *       actually wants JSON!
         */
        if (_object == null)
            return true;

        if (_object instanceof String)
            return true;
        if (_object instanceof Boolean)
            return true;
        if (_object instanceof Number)
            return true;
        if (_object instanceof List)
            return true;
        if (_object instanceof Map)
            return true;
        if (_object instanceof Throwable)
            return true;
        if (_object instanceof Date)
            return true;

        final Class itemClazz = _object.getClass().getComponentType();
        if (itemClazz != null) { /* an array */
            // FIXME: check componenttype, refactor this section to be recursive
            return true;
        }

        log.warn("object type unsupported by this renderer: " + _object.getClass());
        return false;
    }

    /* rendering */

    public Exception renderObjectInContext(final Object _object, WOContext _ctx) {
        final StringBuilder json = new StringBuilder(4096);
        final Exception error = this.appendObjectToString(_object, json);
        if (error != null)
            return error;

        // check for JSONP
        final WORequest rq = _ctx.request();
        String cb = null;
        if (rq != null) {
            cb = rq.stringFormValueForKey("callback");
            if (cb == null || cb.length() == 0)
                cb = null;
        }

        final WOResponse r = _ctx.response();
        r.setContentEncoding("utf8");
        if (cb != null) // JSONP
            r.setHeaderForKey("application/javascript; charset=utf-8", "content-type");
        else
            r.setHeaderForKey("application/json; charset=utf-8", "content-type");

        /* Support httpStatus in NSException's, gives the exception a little
         * control. */
        if (_object instanceof NSException) {
            int status = UObject.intValue(((NSException) _object).valueForKey("httpStatus"));
            if (status >= 200 && status < 1000)
                r.setStatus(status);
            else
                r.setStatus(500);
        }

        r.enableStreaming();
        if (cb != null)
            r.appendContentString(cb + "(");
        r.appendContentString(json.toString());
        if (cb != null)
            r.appendContentString(");");
        return null;
    }

    /* JSON rendering */

    public static final String[] JSEscapeList = { "\\", "\\\\", "'", "\\'", "\"", "\\\"", "\n", "\\n", "\b", "\\b",
            "\f", "\\f", "\r", "\\r", "\t", "\\t", };

    public Exception appendObjectToString(Object _object, StringBuilder _sb) {
        if (_object == null) {
            _sb.append("null");
            return null;
        }

        if (_object instanceof GoJSONResult)
            return this.appendObjectToString(((GoJSONResult) _object).result(), _sb);

        if (_object instanceof String) {
            String s = (String) _object;
            _sb.append("\"");
            _sb.append(UString.replaceInSequence(s, JSEscapeList));
            _sb.append("\"");
            return null;
        }

        if (_object instanceof Boolean) {
            _sb.append(((Boolean) _object).booleanValue() ? "true" : "false");
            return null;
        }

        if (_object instanceof Number) {
            _sb.append(_object);
            return null;
        }

        if (_object instanceof List)
            return this.appendListToString((List) _object, _sb);

        if (_object instanceof Map)
            return this.appendMapToString((Map) _object, _sb);

        if (_object instanceof Throwable)
            return this.appendExceptionToString((Throwable) _object, _sb);

        final Class itemClazz = _object.getClass().getComponentType();
        if (itemClazz != null) /* an array */
            return this.appendListToString(UList.asList(_object), _sb);

        if (_object instanceof Date)
            return this.appendDateToString((Date) _object, _sb);

        return this.appendCustomObjectToString(_object, _sb);
    }

    /* specific appenders */

    public Exception appendExceptionToString(Throwable _ex, StringBuilder _sb) {
        if (_ex == null) {
            _sb.append("null");
            return null;
        }

        Exception error;
        _sb.append('{');

        /* add message code */

        Object v = null;

        if (v == null) {
            try {
                v = NSKeyValueCoding.Utility.valueForKey(_ex, "code");
            } catch (MissingPropertyException e) {
            } /* we do not care and continue */
        }
        if (v == null) {
            try {
                v = NSKeyValueCoding.Utility.valueForKey(_ex, "errorCode");
            } catch (MissingPropertyException e) {
            } /* we do not care and continue */
        }

        if (_ex instanceof SQLException)
            v = "sql" + ((SQLException) _ex).getSQLState();

        if (v == null)
            v = _ex.getClass().getName();

        if (v == null)
            v = "unknown";

        error = this.appendKeyValuePair("error", v, true, _sb);
        if (error != null)
            return error;

        /* added messages */

        final String pm = _ex.getMessage();
        String sm = _ex.getLocalizedMessage();
        if (pm == sm || (pm != null && sm != null && pm.equals(sm)))
            sm = null;

        error = this.appendKeyValuePair("message", pm, false, _sb);
        if (error != null)
            return error;

        error = this.appendKeyValuePair("localizedMessage", sm, false, _sb);
        if (error != null)
            return error;

        /* additional standard keys */

        v = null;
        try {
            v = NSKeyValueCoding.Utility.valueForKey(_ex, "httpStatus");
        } catch (MissingPropertyException e) {
        } /* we do not care and continue */

        error = this.appendKeyValuePair("httpStatus", v, false, _sb);
        if (error != null)
            return error;

        if (_ex instanceof SQLException) {
            error = this.appendKeyValuePair("sqlstate", ((SQLException) _ex).getSQLState(), false, _sb);
            if (error != null)
                return error;
        }

        _sb.append('}');
        return null;
    }

    public Exception appendKeyValuePair(Object _key, Object _value, boolean _isFirst, StringBuilder _sb) {
        if (_value == null)
            return null;

        if (!_isFirst)
            _sb.append(",");
        Exception error = this.appendObjectToString(_key, _sb);
        if (error != null)
            return error;
        _sb.append(':');
        return this.appendObjectToString(_value, _sb);
    }

    public Exception appendListToString(List _list, StringBuilder _sb) {
        if (_list == null) {
            _sb.append("null");
            return null;
        }

        _sb.append('[');

        boolean isFirst = true;
        for (Object value : _list) {
            if (isFirst)
                isFirst = false;
            else
                _sb.append(',');

            Exception error = this.appendObjectToString(value, _sb);
            if (error != null)
                return error;
        }

        _sb.append(']');
        return null;
    }

    public Exception appendMapToString(Map _map, StringBuilder _sb) {
        if (_map == null) {
            _sb.append("null");
            return null;
        }

        _sb.append('{');

        boolean isFirst = true;
        for (Object key : _map.keySet()) {
            if (isFirst)
                isFirst = false;
            else
                _sb.append(',');

            if (!(key instanceof String)) {
                return new GoInternalErrorException("cannot render given object " + "as JSON");
            }
            Exception error = this.appendObjectToString(key, _sb);
            if (error != null)
                return error;

            _sb.append(':');
            error = this.appendObjectToString(_map.get(key), _sb);
            if (error != null)
                return error;
        }

        _sb.append('}');
        return null;
    }

    public Exception appendDateToString(final Date _ts, StringBuilder _sb) {
        if (_ts == null) {
            _sb.append("null");
            return null;
        }

        _sb.append('"');
        _sb.append(this.dateFormat.format(_ts));
        _sb.append('"');
        return null;
    }

    public Exception appendCustomObjectToString(Object _obj, StringBuilder _sb) {
        if (_obj instanceof Exception)
            return (Exception) _obj;

        log.warn("cannot render object as JSON: " + _obj + " (" + _obj.getClass() + ")");
        return new GoInternalErrorException("cannot render given object as JSON");
    }
}