diff --git a/source/java/org/alfresco/util/JSONtoFmModel.java b/source/java/org/alfresco/util/JSONtoFmModel.java new file mode 100644 index 0000000000..50dc8887c8 --- /dev/null +++ b/source/java/org/alfresco/util/JSONtoFmModel.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2005-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.util; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONTokener; + +/** + * Utility to convert JSON to Freemarker-compatible data model + * + * @author janv + */ +public final class JSONtoFmModel +{ + public static String ROOT_ARRAY = "root"; + + // note: current format is dependent on ISO8601DateFormat.parser, eg. YYYY-MM-DDThh:mm:ss.sssTZD + private static String REGEXP_ISO8061 = "^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(.([0-9]){3})?(Z|[\\+\\-]([0-9]{2}):([0-9]{2}))$"; + private static Pattern matcherISO8601 = Pattern.compile(REGEXP_ISO8061); + + public static boolean autoConvertISO8601 = true; + + /** + * Convert JSON Object string to Freemarker-compatible data model + * + * @param jsonString + * @return model + * @throws JSONException + */ + public static Map convertJSONObjectToMap(String jsonString) throws JSONException + { + JSONObject jo = new JSONObject(new JSONTokener(jsonString)); + return convertJSONObjectToMap(jo); + } + + /** + * JSONObject is an unordered collection of name/value pairs -> convert to Map (equivalent to Freemarker "hash") + */ + @SuppressWarnings("unchecked") + public static Map convertJSONObjectToMap(JSONObject jo) throws JSONException + { + Map model = new HashMap(); + + Iterator itr = (Iterator)jo.keys(); + while (itr.hasNext()) + { + String key = (String)itr.next(); + + Object o = jo.get(key); + if (o instanceof JSONObject) + { + model.put(key, convertJSONObjectToMap((JSONObject)o)); + } + else if (o instanceof JSONArray) + { + model.put(key, convertJSONArrayToList((JSONArray)o)); + } + else if (o == JSONObject.NULL) + { + model.put(key, null); // note: http://freemarker.org/docs/dgui_template_exp.html#dgui_template_exp_missing + } + else + { + if ((o instanceof String) && autoConvertISO8601 && (matcherISO8601.matcher((String)o).matches())) + { + o = ISO8601DateFormat.parse((String)o); + } + + model.put(key, o); + } + } + + return model; + } + + /** + * Convert JSON Array string to Freemarker-compatible data model + * + * @param jsonString + * @return model + * @throws JSONException + */ + public static Map convertJSONArrayToMap(String jsonString) throws JSONException + { + Map model = new HashMap(); + JSONArray ja = new JSONArray(new JSONTokener(jsonString)); + model.put(ROOT_ARRAY, convertJSONArrayToList(ja)); + return model; + } + + /** + * JSONArray is an ordered sequence of values -> convert to List (equivalent to Freemarker "sequence") + */ + public static List convertJSONArrayToList(JSONArray ja) throws JSONException + { + List model = new ArrayList(); + + for (int i = 0; i < ja.length(); i++) + { + Object o = ja.get(i); + + if (o instanceof JSONArray) + { + model.add(convertJSONArrayToList((JSONArray)o)); + } + else if (o instanceof JSONObject) + { + model.add(convertJSONObjectToMap((JSONObject)o)); + } + else if (o == JSONObject.NULL) + { + model.add(null); + } + else + { + if ((o instanceof String) && autoConvertISO8601 && (matcherISO8601.matcher((String)o).matches())) + { + o = ISO8601DateFormat.parse((String)o); + } + + model.add(o); + } + } + + return model; + } + + // for debugging + public static String toString(Map map) + { + return JSONtoFmModel.toStringBuffer(map, 0).toString(); + } + + @SuppressWarnings("unchecked") + private static StringBuffer toStringBuffer(Map map, int indent) + { + StringBuffer tabs = new StringBuffer(); + for (int i = 0; i < indent; i++) + { + tabs.append("\t"); + } + + StringBuffer sb = new StringBuffer(); + + for (Map.Entry entry : map.entrySet()) + { + if (entry.getValue() instanceof Map) + { + sb.append(tabs).append(entry.getKey()).append(":").append(entry.getValue().getClass()).append("\n"); + sb.append(JSONtoFmModel.toStringBuffer((Map)entry.getValue(), indent+1)); + } + else if (entry.getValue() instanceof List) + { + sb.append(tabs).append("[\n"); + List l = (List)entry.getValue(); + for (int i = 0; i < l.size(); i++) + { + sb.append(tabs).append(l.get(i)).append(":").append((l.get(i) != null) ? l.get(i).getClass() : "null").append("\n"); + } + sb.append(tabs).append("]\n"); + } + else + { + sb.append(tabs).append(entry.getKey()).append(":").append(entry.getValue()).append(":").append((entry.getValue() != null ? entry.getValue().getClass() : "null")).append("\n"); + } + } + + return sb; + } +} diff --git a/source/java/org/alfresco/util/JSONtoFmModelTest.java b/source/java/org/alfresco/util/JSONtoFmModelTest.java new file mode 100644 index 0000000000..c1a7c420b9 --- /dev/null +++ b/source/java/org/alfresco/util/JSONtoFmModelTest.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2005-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.util; + +import java.io.File; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.Map; + +import junit.framework.TestCase; +import freemarker.template.Configuration; +import freemarker.template.ObjectWrapper; +import freemarker.template.Template; + +/** + * Test JSONtoFmModel conversion + * + * Note: Dates depend on ISO8601DateFormat.parse which currently expects YYYY-MM-DDThh:mm:ss.sssTZD + * + * @author janv + */ +public class JSONtoFmModelTest extends TestCase +{ + + public void testUtil() + { + String test1_in = "[ 123, \"hello\", true, null, \"1994-11-05T13:15:30.123-12:30\"]"; + + String test1_expected_out = + "[\n" + + "123:class java.lang.Integer\n" + + "hello:class java.lang.String\n" + + "true:class java.lang.Boolean\n" + + "null:null\n" + + "Sun Nov 06 01:45:30 GMT 1994:class java.util.Date\n" + + "]\n"; + + String test2_in = "{ \"glossary\": { \"title\": \"example glossary\", } }"; + + String test2_expected_out = + "glossary:class java.util.HashMap\n" + + "\ttitle:example glossary:class java.lang.String\n"; + + String test3_in = + "{ \"doc\": " + + " { \"abc\": \"hello\", " + + " \"def\": \"world\", " + + " \"ghi\" : 123, " + + " \"jkl\" : 123.456, " + + " \"mno\" : true, " + + " \"qrs\" : \"1994-11-05T13:15:30.000Z\"" + + " }" + + "}"; + + String test3_expected_out = + "doc:class java.util.HashMap\n" + + "\tghi:123:class java.lang.Integer\n" + + "\tmno:true:class java.lang.Boolean\n" + + "\tqrs:Sat Nov 05 13:15:30 GMT 1994:class java.util.Date\n" + + "\tdef:world:class java.lang.String\n" + + "\tabc:hello:class java.lang.String\n" + + "\tjkl:123.456:class java.lang.Double\n"; + + String test4_in = + "{" + + " \"glossary\": {" + + " \"title\": \"example glossary\"," + + " \"GlossDiv\": {" + + " \"title\": \"S\"," + + " \"GlossList\": {" + + " \"GlossEntry\": {" + + " \"ID\": \"SGML\"," + + " \"SortAs\": \"SGML\"," + + " \"GlossTerm\": \"Standard Generalized Markup Language\", " + + " \"Acronym\": \"SGML\"," + + " \"Abbrev\": \"ISO 8879:1986\"," + + " \"GlossDef\": {" + + " \"para\": \"A meta-markup language, used to create markup languages such as DocBook.\","+ + " \"GlossSeeAlso\": [\"GML\", \"XML\", \"ANO1\", \"ANO2\"]" + + " }," + + " \"GlossSee\": \"markup\"" + + " }" + + " }" + + " }" + + " }" + + "}"; + + String test4_expected_out = + "glossary:class java.util.HashMap\n" + + "\ttitle:example glossary:class java.lang.String\n" + + "\tGlossDiv:class java.util.HashMap\n" + + "\t\ttitle:S:class java.lang.String\n" + + "\t\tGlossList:class java.util.HashMap\n" + + "\t\t\tGlossEntry:class java.util.HashMap\n" + + "\t\t\t\tGlossTerm:Standard Generalized Markup Language:class java.lang.String\n" + + "\t\t\t\tSortAs:SGML:class java.lang.String\n" + + "\t\t\t\tAbbrev:ISO 8879:1986:class java.lang.String\n" + + "\t\t\t\tGlossDef:class java.util.HashMap\n" + + "\t\t\t\t\tpara:A meta-markup language, used to create markup languages such as DocBook.:class java.lang.String\n" + + "\t\t\t\t\t[\n" + + "\t\t\t\t\tGML:class java.lang.String\n" + + "\t\t\t\t\tXML:class java.lang.String\n" + + "\t\t\t\t\tANO1:class java.lang.String\n" + + "\t\t\t\t\tANO2:class java.lang.String\n" + + "\t\t\t\t\t]\n" + + "\t\t\t\tAcronym:SGML:class java.lang.String\n" + + "\t\t\t\tGlossSee:markup:class java.lang.String\n" + + "\t\t\t\tID:SGML:class java.lang.String\n"; + + try + { + Configuration cfg = new Configuration(); + cfg.setObjectWrapper(ObjectWrapper.DEFAULT_WRAPPER); + + String userDir = System.getProperty("user.dir"); + System.out.println(userDir); + cfg.setDirectoryForTemplateLoading(new File(userDir+"/source/test-resources/JSONtoFmModel")); + + Map root = null; + + // Test 1 + System.out.println("TEST 1"); + //System.out.println(test1_in); + //System.out.println("--->"); + root = JSONtoFmModel.convertJSONArrayToMap(test1_in); + //System.out.println(JSONtoFmModel.toString(root)); + assertEquals(test1_expected_out, JSONtoFmModel.toString(root)); + + Template temp = cfg.getTemplate("test1.ftl"); + Writer out = new OutputStreamWriter(System.out); + temp.process(root, out); + out.flush(); + + System.out.println("\n\n\n"); + + // Test 2 + System.out.println("TEST 2"); + //System.out.println(test2_in); + //System.out.println("--->"); + root = JSONtoFmModel.convertJSONObjectToMap(test2_in); + //System.out.println(JSONtoFmModel.toString(root)); + assertEquals(test2_expected_out, JSONtoFmModel.toString(root)); + + temp = cfg.getTemplate("test2.ftl"); + out = new OutputStreamWriter(System.out); + temp.process(root, out); + out.flush(); + + System.out.println("\n\n\n"); + + // Test 3 + System.out.println("TEST 3"); + //System.out.println(test3_in); + //System.out.println("--->"); + root = JSONtoFmModel.convertJSONObjectToMap(test3_in); + //System.out.println(JSONtoFmModel.toString(root)); + assertEquals(test3_expected_out, JSONtoFmModel.toString(root)); + + temp = cfg.getTemplate("test3.ftl"); + out = new OutputStreamWriter(System.out); + temp.process(root, out); + out.flush(); + + // Test 4 + System.out.println("TEST 4"); + //System.out.println(test4_in); + //System.out.println("--->"); + root = JSONtoFmModel.convertJSONObjectToMap(test4_in); + //System.out.println(JSONtoFmModel.toString(root)); + assertEquals(test4_expected_out, JSONtoFmModel.toString(root)); + + temp = cfg.getTemplate("test4.ftl"); + out = new OutputStreamWriter(System.out); + temp.process(root, out); + out.flush(); + } + catch (Exception e) + { + System.out.println("ERROR: " + e); + } + } +} diff --git a/source/test-resources/JSONtoFmModel/test1.ftl b/source/test-resources/JSONtoFmModel/test1.ftl new file mode 100644 index 0000000000..ac48b68447 --- /dev/null +++ b/source/test-resources/JSONtoFmModel/test1.ftl @@ -0,0 +1,5 @@ +

${root[0]}

+

${root[1]}

+

${root[2]?string("true","false")}

+

${root[3]!""}

+

${root[4]?datetime?string.medium_medium}
\ No newline at end of file diff --git a/source/test-resources/JSONtoFmModel/test2.ftl b/source/test-resources/JSONtoFmModel/test2.ftl new file mode 100644 index 0000000000..6a946c9e24 --- /dev/null +++ b/source/test-resources/JSONtoFmModel/test2.ftl @@ -0,0 +1 @@ +

${glossary.title}

diff --git a/source/test-resources/JSONtoFmModel/test3.ftl b/source/test-resources/JSONtoFmModel/test3.ftl new file mode 100644 index 0000000000..8f3f4b96df --- /dev/null +++ b/source/test-resources/JSONtoFmModel/test3.ftl @@ -0,0 +1,7 @@ +

${doc.abc}

+

${doc.def}

+

${doc.ghi}

+

${doc.jkl}

+

${doc.mno?string("yes", "no")}

+

${doc.qrs?datetime?string.long}

+ diff --git a/source/test-resources/JSONtoFmModel/test4.ftl b/source/test-resources/JSONtoFmModel/test4.ftl new file mode 100644 index 0000000000..7b67ded1e9 --- /dev/null +++ b/source/test-resources/JSONtoFmModel/test4.ftl @@ -0,0 +1,13 @@ +

${glossary.title}

+

${glossary.GlossDiv.title}

+

${glossary.GlossDiv.GlossList.GlossEntry.ID}

+

${glossary.GlossDiv.GlossList.GlossEntry.SortAs}

+

${glossary.GlossDiv.GlossList.GlossEntry.GlossTerm}

+

${glossary.GlossDiv.GlossList.GlossEntry.Acronym}

+

${glossary.GlossDiv.GlossList.GlossEntry.Abbrev}

+

${glossary.GlossDiv.GlossList.GlossEntry.GlossSee}

+

${glossary.GlossDiv.GlossList.GlossEntry.GlossDef.para}

+

${glossary.GlossDiv.GlossList.GlossEntry.GlossDef.GlossSeeAlso[0]}

+

${glossary.GlossDiv.GlossList.GlossEntry.GlossDef.GlossSeeAlso[1]}

+

${glossary.GlossDiv.GlossList.GlossEntry.GlossDef.GlossSeeAlso[2]}

+

${glossary.GlossDiv.GlossList.GlossEntry.GlossDef.GlossSeeAlso[3]}