/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program; if not, see http://www.gnu.org/licenses or write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */ package com.servoy.j2db.util.serialize; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; import org.jabsorb.JSONSerializer; import org.jabsorb.serializer.MarshallException; import org.jabsorb.serializer.SerializerState; import org.jabsorb.serializer.UnmarshallException; import org.jabsorb.serializer.impl.ArraySerializer; import org.jabsorb.serializer.impl.BeanSerializer; import org.jabsorb.serializer.impl.BooleanSerializer; import org.jabsorb.serializer.impl.DateSerializer; import org.jabsorb.serializer.impl.DictionarySerializer; import org.jabsorb.serializer.impl.ListSerializer; import org.jabsorb.serializer.impl.MapSerializer; import org.jabsorb.serializer.impl.NumberSerializer; import org.jabsorb.serializer.impl.PrimitiveSerializer; import org.jabsorb.serializer.impl.RawJSONArraySerializer; import org.jabsorb.serializer.impl.RawJSONObjectSerializer; import org.jabsorb.serializer.impl.SetSerializer; import org.jabsorb.serializer.impl.StringSerializer; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.mozilla.javascript.NativeArray; import org.mozilla.javascript.NativeObject; import org.mozilla.javascript.Wrapper; import com.servoy.j2db.dataprocessing.IDatabaseManager; import com.servoy.j2db.querybuilder.impl.QBFactory; import com.servoy.j2db.util.Debug; /** * Wrapper for JSONSerializer, handles a few exceptions to the default JSONSerializer and adds a defaultSerializer for cases when no class hint is given. * * @author rgansevles * */ @SuppressWarnings("nls") public class JSONSerializerWrapper implements IQueryBuilderFactoryProvider { private JSONSerializer serializer; private final boolean handleByteArrays; ThreadLocal<IDatabaseManager> currentDBMGR = new ThreadLocal<IDatabaseManager>(); public JSONSerializerWrapper(boolean handleByteArrays) { this.handleByteArrays = handleByteArrays; } public Object toJSON(Object obj) throws MarshallException { if (obj instanceof String) return JSONObject.quote((String)obj); SerializerState state = new SerializerState(); return getSerializer().marshall(state, null, wrapToJSON(obj), "result"); } public Object fromJSON(IDatabaseManager databaseManager, String data) throws UnmarshallException { if (databaseManager == null) { return fromJSON(data); } IDatabaseManager tmp = currentDBMGR.get(); try { currentDBMGR.set(databaseManager); return fromJSON(data); } finally { currentDBMGR.set(tmp); } } public Object fromJSON(String data) throws UnmarshallException { try { return unwrapFromJSON(getSerializer().fromJSON(data)); } catch (UnmarshallException e) { Debug.error(e); throw e; } } public Object fromJSON(IDatabaseManager databaseManager, JSONObject json) throws UnmarshallException { if (databaseManager == null) { return fromJSON(json); } IDatabaseManager tmp = currentDBMGR.get(); try { currentDBMGR.set(databaseManager); return fromJSON(json); } finally { currentDBMGR.set(tmp); } } public Object fromJSON(JSONObject json) throws UnmarshallException { SerializerState state = new SerializerState(); return unwrapFromJSON(getSerializer().unmarshall(state, null, json)); } protected synchronized JSONSerializer getSerializer() { if (serializer == null) { serializer = new JSONSerializer() { @Override public Object marshall(SerializerState state, Object parent, Object java, Object ref) throws MarshallException { // NativeArray may contain wrapped data return super.marshall(state, parent, wrapToJSON(java), ref); } @Override public Object unmarshall(SerializerState state, Class clazz, Object json) throws UnmarshallException { if ((clazz == null || clazz == Object.class) && json instanceof JSONObject && !((JSONObject)json).has("javaClass")) { // NativeObjectSerializer when there is no class hint clazz = NativeObject.class; } if (clazz == null && json instanceof JSONArray) { // default native array when there is no class hint clazz = NativeArray.class; } if ((clazz == null || clazz == Object.class) && json instanceof Boolean) { // hack to make sure BooleanSerializer is used clazz = Boolean.class; } return super.unmarshall(state, clazz, json); } @Override public boolean isPrimitive(Object o) { if (o != null) { Class cls = o.getClass(); if (cls == java.math.BigDecimal.class || cls == java.math.BigInteger.class) { return true; } } return super.isPrimitive(o); } @Override protected Class getClassFromHint(Object o) throws UnmarshallException { if (o == null) { return null; } if (o instanceof JSONObject) { String className = "(unknown)"; try { className = ((JSONObject)o).getString("javaClass"); return Class.forName(className); } catch (Exception e) { throw new UnmarshallException("Class specified in javaClass hint not found: " + className, e); } } if (o instanceof JSONArray) { JSONArray arr = (JSONArray)o; if (arr.length() == 0) { // assume Object array (best guess) return Object[].class; } // return type of first element Class compClazz; try { compClazz = getClassFromHint(arr.get(0)); } catch (JSONException e) { throw (NoSuchElementException)new NoSuchElementException(e.getMessage()).initCause(e); } try { if (compClazz.isArray()) { return Class.forName("[" + compClazz.getName()); } return Class.forName("[L" + compClazz.getName() + ";"); } catch (ClassNotFoundException e) { throw new UnmarshallException("problem getting array type", e); } } return o.getClass(); } }; try { serializer.setFixupDuplicates(false); // registerDefaultSerializers serializer.registerSerializer(new BeanSerializer()); // least-specific serializers first, they will be selected last if no other serializer matches serializer.registerSerializer(new RawJSONArraySerializer() { @Override public boolean canSerialize(Class clazz, Class jsonClazz) { // make sure JSONArray subclasses are also serialized as just the json return JSONArray.class.isAssignableFrom(clazz) && (jsonClazz == null || jsonClazz == JSONArray.class); } }); serializer.registerSerializer(new RawJSONObjectSerializer() { @Override public boolean canSerialize(Class clazz, Class jsonClazz) { // make sure JSONObject subclasses are also serialized as just the json return JSONObject.class.isAssignableFrom(clazz) && (jsonClazz == null || jsonClazz == JSONObject.class); } }); serializer.registerSerializer(new ArraySerializer()); serializer.registerSerializer(new DictionarySerializer()); serializer.registerSerializer(new MapSerializer()); serializer.registerSerializer(new SetSerializer()); serializer.registerSerializer(new ListSerializer()); serializer.registerSerializer(new DateSerializer()); serializer.registerSerializer(handleByteArrays ? new StringByteArraySerializer() : new StringSerializer()); // handle byte arrays as base64 encoded? serializer.registerSerializer(new NumberSerializer()); serializer.registerSerializer(new BooleanSerializer()); serializer.registerSerializer(new PrimitiveSerializer()); serializer.registerSerializer(new QueryBuilderSerializer(this)); serializer.registerSerializer(new NativeObjectSerializer()); serializer.registerSerializer(new NullForUndefinedSerializer()); } catch (Exception e) { Debug.error(e); } } return serializer; } public QBFactory getQueryBuilderFactory() { IDatabaseManager dbmgr = currentDBMGR.get(); if (dbmgr != null) { return (QBFactory)dbmgr.getQueryFactory(); } return null; } /** * * Wrap to serialize with JSONRPC. * * @param obj to serialize to JSON * @return obj ready to be serialized with JSONRPC */ public static Object wrapToJSON(Object object) { // unwrap rhino object, don't unwrap NativeArray, those are handled by the NativeObjectSerializer Object obj = object; if (obj instanceof Wrapper && !(obj instanceof NativeArray)) { obj = ((Wrapper)obj).unwrap(); } return obj; } /** * * Unwrap from JSONRPC serialized object. * * @param obj deserialized from JSON * @return object */ public static Object unwrapFromJSON(Object obj) { // put this back for legacy behavior support, arrays used to be serialized as json objects if (obj instanceof ArrayList) { List<Object> objArrayList = (List<Object>)obj; Object[] plainArray = new Object[objArrayList.size()]; for (int i = 0; i < objArrayList.size(); i++) { plainArray[i] = unwrapFromJSON(objArrayList.get(i)); } return plainArray; } if (obj == JSONObject.NULL) { return null; } return obj; } }