/* * JSONTemplate.java * * Brazil project web application toolkit, * export version: 2.3 * Copyright (c) 2008-2009 Sun Microsystems, Inc. * * Sun Public License Notice * * The contents of this file are subject to the Sun Public License Version * 1.0 (the "License"). You may not use this file except in compliance with * the License. A copy of the License is included as the file "license.terms", * and also available at http://www.sun.com/ * * The Original Code is from: * Brazil project web application toolkit release 2.3. * The Initial Developer of the Original Code is: suhler. * Portions created by suhler are Copyright (C) Sun Microsystems, Inc. * All Rights Reserved. * * Contributor(s): suhler. * * Version: 1.9 * Created by suhler on 08/09/17 * Last modified by suhler on 09/07/13 14:17:35 * * Version Histories: * * 1.9 09/07/13-14:17:35 (suhler) * doc fixes * * 1.8 09/07/13-13:54:05 (suhler) * add the capability to "merge" json objects * * 1.7 09/07/13-12:04:09 (suhler) * - deprecate incorporating its functionality into * * * 1.6 09/07/08-07:47:38 (suhler) * doc fixes * * 1.5 09/07/06-08:38:09 (suhler) * added inline values for * * 1.4 09/06/12-13:27:02 (suhler) * - convert properties -> json recursively * - add a json -> properties converter as well * * 1.3 08/09/19-13:59:30 (suhler) * both JSON styles work: need to deal with errors properly * * 1.2 08/09/19-10:31:19 (suhler) * mess up the vm, * * 1.2 70/01/01-00:00:02 (Codemgr) * SunPro Code Manager data about conflicts, renames, etc... * Name history : 1 0 json/JSONTemplate.java * * 1.1 08/09/17-15:23:39 (suhler) * date and time created 08/09/17 15:23:39 by suhler * */ package sunlabs.brazil.json; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONArray; import sunlabs.brazil.server.Request; import sunlabs.brazil.template.RewriteContext; import sunlabs.brazil.template.Template; import sunlabs.brazil.util.Format; import java.util.StringTokenizer; import java.util.Enumeration; import java.util.Vector; import java.util.Stack; import java.util.Properties; /** * Template for generating (or extracting) JSON format data. *
<json2props src="json text" prefix="..." [delim="..."]> *
Flatten JSON (supplied in "src") into a set of properties prefixed * by "prefix". This is the inverse of the * <item, prefix="..."> * tag (if the delim is ".", the default). *

* The <json2props> tag can be used to flatten a json object into * a set of properties, which can be manipulate as a set of name/value * pairs using the * standard mechanisms, then turned it back into a JSON object again. * *

* The following set of templates can be used to generate a JSON object via * an *ML representation. *

*
<array> ... </array> *
Create a JSON array *
<object> ... </object> *
create a JSON object *
<item value=xxxx /> *
<item valueinline /> ... </item> *
Add an element onto a JSON array *
<item name=xxx value=xxxx /> *
<item name=xxx> ... </item> *
<item name=xxx valueinline /> ... </item> *
Add an entry into a JSON object. *
<item prepend=xxx /> *
<item name=xxx prepend=xxx /> *
Add items onto a JSON array or object from a set of Properties. *
* See the discussion under <item> for more specifics. *

* All the markup in the current template is replaced * with the JSON generated by the "object", "array" and "item" tags. *

    *
  • Error handling is currently incomplete, the code tries to * to guess what is intended in the face of errors. *
*/ public class JSONTemplate extends Template { Vector items = new Vector(); // list of items to JSONify Stack stack = new Stack(); // Json Stack // Stack stack = new MyStack(); // Json Stack public boolean init(RewriteContext hr) { items.setSize(0); stack.setSize(0); return super.init(hr); } /** * This tag is used to map a set of properties to a * JSON object. If more than one tag is present, a JSON array will be produced. *
Deprecated - use the "prefix" attribute of the "item" tag instead. */ public void tag_json(RewriteContext hr) { String pre = hr.get("prefix"); hr.killToken(); if (pre!= null) { items.addElement(pre); } return; } public void tag_object(RewriteContext hr) { hr.killToken(); stack.push(new JSONObject()); } public void tag_slash_object(RewriteContext hr) { hr.killToken(); if (!(stack.peek() instanceof JSONObject)) { oops(hr, "need "); } popHelper(hr); } public void tag_array(RewriteContext hr) { hr.killToken(); stack.push(new JSONArray()); } public void tag_slash_array(RewriteContext hr) { hr.killToken(); if (!(stack.peek() instanceof JSONArray)) { oops(hr, "need "); } popHelper(hr); } /** * If this is a singleton, then the name must be defined if in the * context of an "object", and "value" must be defined in in the * context of an "array". *

* Otherwise, "name" must be defined, and there should be either * an <array>..</array> or * <object>..</object> tag pair before the * enclosing </item>. *

* if "valueinline" is defined, then all the markup until the * matching closing </item> is taken as the value. The * additional boolean attributes "trim" and "eval" can be used * to trim whitespace from, and evaluate ${...} constructs from * the value respectively. * * If "prefix" is defined instead of "value" or "valueinline" * then the value of the object (or array element if name is missing * and the current context is an array), the object value is generated * implicitly from the current request properties of the described * prefix. An optional attribute "delim" (which default to ".") is * used to allow the generation of nested values. *

* If the name attribute is missing with "prefix", * and the current context is an * <object>, then the JSON object implied by the "prefix" is * merged into the existing object. So, if the property is defined: *

     * foo.I=am here
     * 
* Then The markup: *
     * <object>
     *   <item name="test" value="ing" />
     *   <item name=other prefix=foo />
     * </object>
     * 
* produces: *
     *{
     *  "other": {"I": "am here"},
     *  "test": "ing"
     *}
     * 
* Whereas without the "name" attribute the markup *
     * <object>
     *   <item name="test" value="ing" />
     *   <item prefix=foo />
     * </object>
     * 
* will produce: *
     *{
     *  "I": "am here",
     *  "test": "ing"
     *}
     * 
*

* Each property whose name is [prefix].a.b. ... n is * created as * a node in the resulting json object. Any objects whose entire entries * consist of "0, 1, ... n" are converted into an array. * Similarly, the values * "true", "false", and "null" are treated as JSON booleans (or null). * numbers are converted into JSON integers. *
[Note: it is not possible to distinquish booleans, nulls and * integers from * their string equivalents: Properties only deal with strings.] *

* For example, if the following properties are defined: *

     *  foo.a=hi
     *  foo.b.0=nothing
     *  foo.b.1=something
     *  foo.number=27
     *  foo.ok=true
     * 
* Then the markup: * <object> * <item name=implicit prefix="foo" /> * </object> * will produce the output: *
     *{"implicit": {
     *  "a": "hi",
     *  "b": [
     *    "nothing",
     *    "something"
     *  ],
     *  "number": 27,
     *  "ok": true
     *}}
     * 
*/ public void tag_item(RewriteContext hr) { hr.killToken(); if (stack.size() == 0) { stack.push(new JSONObject()); oops(hr, "inserting implied object"); } String name = hr.get("name"); String prefix = hr.get("prefix"); Object parent = stack.peek(); boolean single = hr.isSingleton(); Object jv = JsonValue(hr.get("value")); // json value of item // use the enclosed markup as the value if (hr.isTrue("valueinline") && !single) { boolean trim = hr.isTrue("trim"); boolean eval = hr.isTrue("eval"); String value = hr.snarfTillClose(); single = true; if (trim) { value=value.trim(); } if (eval) { jv = JsonValue(Format.subst(hr.request.props, value, true)); } else { jv = JsonValue(value); } // create json value implied by properties prefix } else if (single && prefix != null) { try { jv = deflatten(hr.request.props, prefix, hr.get("delim",".")); } catch (JSONException e) { debug(hr, "Invalid json: " + e); } } if (!single && name!=null) { stack.push(new Item(name)); } else if (parent instanceof JSONObject && name!=null) { try { ((JSONObject)parent).put(name, jv); } catch (JSONException e) { oops(hr, "invalid item" + e); } } else if (parent instanceof JSONArray) { ((JSONArray)parent).put(jv); } else if (parent instanceof JSONObject && name==null && jv instanceof JSONObject) { try { merge((JSONObject)parent, (JSONObject)jv); } catch (JSONException e) { oops(hr, "invalid merge" + e); } } else { oops(hr, "Invalid parent for item"); } } /** * We finished our item: pop and add to our parent (which must be an object) */ public void tag_slash_item(RewriteContext hr) { hr.killToken(); if (stack.size() == 0) { oops(hr, "No matching tag"); return; } if (!(stack.peek() instanceof Item)) { oops(hr, "need item on stack"); return; } Item me = (Item) stack.pop(); JSONObject parent = (JSONObject) stack.peek(); if (me.value != null) { try { parent.put(me.name, me.value); } catch (JSONException e) { oops(hr, "invalid item" + e); } } } /** * Turn a JSON object into a set of properties. * (This is not the proper way to do this - see the list template) */ public void tag_json2props(RewriteContext hr) { String src = hr.get("src").trim(); String prepend=hr.get("prepend", hr.prefix); String delim=hr.get("delim", "."); Properties props = hr.getNamespaceProperties(); Object j=null; hr.killToken(); try { if (src.startsWith("{")) { j = new JSONObject(src); } else if (src.startsWith("[")) { j = new JSONArray(src); } else { throw new JSONException("JSON must start with '{' or '['"); } } catch (JSONException e) { debug(hr, "Invalid json: " + e); } try { flatten(prepend, delim, props, j); } catch (JSONException e) { debug(hr, "Invalid json: " + e); } } /** . * Recursively extract a json object into a set of properties * @param prefix The prefix for this property name * @param delim The delimiter to use between levels * @param p The properties object to store the flattened tree into * @param obj The object to flatten */ public static void flatten(String prefix, String delim, Properties p, Object obj) throws JSONException { if (obj instanceof JSONObject) { JSONObject jo = (JSONObject) obj; String items[] = JSONObject.getNames(jo); for (int i=0; i 1) { oops(hr, "Non terminated markup"); if (show) { System.err.println("parse error: " + toString(stack.pop(),2)); } return super.done(hr); } // Now deal with the tag (Deprecated - to be removed) if (items.isEmpty()) { return super.done(hr); } if (items.isEmpty()) return true; // XXX temp JSONArray ja = null; // JSON array Object jo = null; // JSON object /* replace all the output with JSON */ hr.reset(); // toss any existing markup if (items.size() > 1) { ja = new JSONArray(); } for(int i=0;i 0) { Object o = stack.peek(); System.out.println("- Stack top " + o.getClass() + ": " + o.toString()); } } /** * Class to represent an complex item: * name=[object] or [name=[array] */ static class Item { public String name; public Object value; Item(String name) { this.name = name; this.value = null; } public void put(Object o) { this.value = o; } public String toString() { return "item(" + name + "," + (value==null ? "null" : value.toString()) + ")"; } } /** * For debugging only */ static class MyStack extends Stack { public Object push(Object o) { System.out.println(size() + " Push " + o.getClass() + ": " + o); return super.push(o); } public Object peek() { Object o = super.peek(); System.out.println(size() + " Peek " + o.getClass() + ": " + o); return o; } public Object pop() { Object o = super.pop(); System.out.println(size() + " Pop " + o.getClass() + ": " + o); return o; } } }