diff options
author | Artur Signell <artur@vaadin.com> | 2012-04-18 01:02:48 +0300 |
---|---|---|
committer | Artur Signell <artur@vaadin.com> | 2012-04-18 23:09:02 +0300 |
commit | 6996029d9d521db35f70c0a017e15cb4bdcf4a78 (patch) | |
tree | f0e34e69d477abbfc819ae166234fc5cf60687a2 /src/com/vaadin/terminal/gwt/server/JsonCodec.java | |
parent | 52b072d77298bfaee84fc0c5206307fb183c6a85 (diff) | |
download | vaadin-framework-6996029d9d521db35f70c0a017e15cb4bdcf4a78.tar.gz vaadin-framework-6996029d9d521db35f70c0a017e15cb4bdcf4a78.zip |
Refactored JsonCodec to use a given type for deserialization (#8667)
Diffstat (limited to 'src/com/vaadin/terminal/gwt/server/JsonCodec.java')
-rw-r--r-- | src/com/vaadin/terminal/gwt/server/JsonCodec.java | 456 |
1 files changed, 298 insertions, 158 deletions
diff --git a/src/com/vaadin/terminal/gwt/server/JsonCodec.java b/src/com/vaadin/terminal/gwt/server/JsonCodec.java index 8da8fdc933..0d2b0c2082 100644 --- a/src/com/vaadin/terminal/gwt/server/JsonCodec.java +++ b/src/com/vaadin/terminal/gwt/server/JsonCodec.java @@ -10,6 +10,8 @@ import java.beans.PropertyDescriptor; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -37,6 +39,12 @@ public class JsonCodec implements Serializable { private static Map<Class<?>, String> typeToTransportType = new HashMap<Class<?>, String>(); + /** + * Note! This does not contain primitives. + * <p> + */ + private static Map<String, Class<?>> transportTypeToType = new HashMap<String, Class<?>>(); + static { registerType(String.class, JsonEncoder.VTYPE_STRING); registerType(Connector.class, JsonEncoder.VTYPE_CONNECTOR); @@ -50,8 +58,6 @@ public class JsonCodec implements Serializable { registerType(double.class, JsonEncoder.VTYPE_DOUBLE); registerType(Long.class, JsonEncoder.VTYPE_LONG); registerType(long.class, JsonEncoder.VTYPE_LONG); - // transported as string representation - registerType(Enum.class, JsonEncoder.VTYPE_STRING); registerType(String[].class, JsonEncoder.VTYPE_STRINGARRAY); registerType(Object[].class, JsonEncoder.VTYPE_ARRAY); registerType(Map.class, JsonEncoder.VTYPE_MAP); @@ -61,92 +67,235 @@ public class JsonCodec implements Serializable { private static void registerType(Class<?> type, String transportType) { typeToTransportType.put(type, transportType); + if (!type.isPrimitive()) { + transportTypeToType.put(transportType, type); + } + } + + public static boolean isInternalTransportType(String transportType) { + return transportTypeToType.containsKey(transportType); + } + + public static boolean isInternalType(Type type) { + if (type instanceof Class && ((Class<?>) type).isPrimitive()) { + // All primitive types are handled internally + return true; + } + return typeToTransportType.containsKey(getClassForType(type)); + } + + private static Class<?> getClassForType(Type type) { + if (type instanceof ParameterizedType) { + return (Class<?>) (((ParameterizedType) type).getRawType()); + } else { + return (Class<?>) type; + } + } + + public static String getTransportType(JSONArray encodedValue) + throws JSONException { + return encodedValue.getString(0); + } + + private static Class<?> getType(String transportType) { + return transportTypeToType.get(transportType); } /** - * Convert a JSON array with two elements (type and value) into a - * server-side type, recursively if necessary. + * Decodes the given value and type, restricted to using only internal + * types. * - * @param value - * JSON array with two elements + * @param valueAndType * @param application - * mapper between connector ID and {@link Connector} objects - * @return converted value (does not contain JSON types) * @throws JSONException - * if the conversion fails */ - public static Object decode(JSONArray value, Application application) - throws JSONException { - return decodeVariableValue(value.getString(0), value.get(1), + @Deprecated + public static Object decodeInternalType(JSONArray valueAndType, + Application application) throws JSONException { + String transportType = getTransportType(valueAndType); + return decodeInternalType(getType(transportType), true, valueAndType, application); } - private static Object decodeVariableValue(String variableType, - Object value, Application application) throws JSONException { - Object val = null; - // TODO type checks etc. - if (JsonEncoder.VTYPE_ARRAY.equals(variableType)) { - val = decodeArray((JSONArray) value, application); - } else if (JsonEncoder.VTYPE_LIST.equals(variableType)) { - val = decodeList((JSONArray) value, application); - } else if (JsonEncoder.VTYPE_SET.equals(variableType)) { - val = decodeSet((JSONArray) value, application); - } else if (JsonEncoder.VTYPE_MAP_CONNECTOR.equals(variableType)) { - val = decodeConnectorMap((JSONObject) value, application); - } else if (JsonEncoder.VTYPE_MAP.equals(variableType)) { - val = decodeMap((JSONObject) value, application); - } else if (JsonEncoder.VTYPE_STRINGARRAY.equals(variableType)) { - val = decodeStringArray((JSONArray) value); - } else if (JsonEncoder.VTYPE_STRING.equals(variableType)) { - val = value; - } else if (JsonEncoder.VTYPE_INTEGER.equals(variableType)) { - // TODO handle properly - val = Integer.valueOf(String.valueOf(value)); - } else if (JsonEncoder.VTYPE_LONG.equals(variableType)) { - // TODO handle properly - val = Long.valueOf(String.valueOf(value)); - } else if (JsonEncoder.VTYPE_FLOAT.equals(variableType)) { - // TODO handle properly - val = Float.valueOf(String.valueOf(value)); - } else if (JsonEncoder.VTYPE_DOUBLE.equals(variableType)) { - // TODO handle properly - val = Double.valueOf(String.valueOf(value)); - } else if (JsonEncoder.VTYPE_BOOLEAN.equals(variableType)) { - // TODO handle properly - val = Boolean.valueOf(String.valueOf(value)); - } else if (JsonEncoder.VTYPE_CONNECTOR.equals(variableType)) { - val = application.getConnector(String.valueOf(value)); - } else if (JsonEncoder.VTYPE_NULL.equals(variableType)) { - val = null; + public static <T> T decodeInternalOrCustomType(Type targetType, + JSONArray valueAndType, Application application) + throws JSONException { + if (isInternalType(targetType)) { + return decodeInternalType(targetType, false, valueAndType, + application); } else { - // Try to decode object using fields - return decodeObject(variableType, (JSONObject) value, application); + return decodeCustomType(targetType, valueAndType, application); + } + } + public static <T> T decodeCustomType(Type targetType, + JSONArray valueAndType, Application application) + throws JSONException { + if (isInternalType(targetType)) { + throw new JSONException("decodeCustomType cannot be used for " + + targetType + ", which is an internal type"); + } + String transportType = getCustomTransportType(getClassForType(targetType)); + String encodedTransportType = valueAndType.getString(0); + if (!encodedTransportType.equals(transportType)) { + throw new JSONException("Expected a value of type " + transportType + + ", received " + encodedTransportType); } - return val; + // Try to decode object using fields + return decodeObject(targetType, (JSONObject) valueAndType.get(1), + application); } - private static Object decodeMap(JSONObject jsonMap, Application application) - throws JSONException { + /** + * Decodes a value that is of an internal type. + * <p> + * Ensures the encoded value is of the same type as target type. + * </p> + * <p> + * Allows restricting collections so that they must be declared using + * generics. If this is used then all objects in the collection are encoded + * using the declared type. Otherwise only internal types are allowed in + * collections. + * </p> + * + * @param targetType + * The type that should be returned by this method + * @param valueAndType + * The encoded value and type array + * @param application + * A reference to the application + * @param enforceGenericsInCollections + * true if generics should be enforce, false to only allow + * internal types in collections + * @return + * @throws JSONException + */ + public static <T> T decodeInternalType(Type targetType, + boolean restrictToInternalTypes, JSONArray valueAndType, + Application application) throws JSONException { + if (!isInternalType(targetType)) { + throw new JSONException("Cannot decode internal type. Type " + + targetType + " is not supported."); + } + String transportType = getInternalTransportType(targetType); + String encodedTransportType = valueAndType.getString(0); + if (!encodedTransportType.equals(transportType)) { + throw new JSONException("Expected a value of type " + transportType + + ", received " + encodedTransportType); + } + + Object encodedJsonValue = valueAndType.get(1); + + // Collections + if (JsonEncoder.VTYPE_LIST.equals(transportType)) { + return (T) decodeList(targetType, restrictToInternalTypes, + (JSONArray) encodedJsonValue, application); + } else if (JsonEncoder.VTYPE_SET.equals(transportType)) { + return (T) decodeSet(targetType, restrictToInternalTypes, + (JSONArray) encodedJsonValue, application); + } else if (JsonEncoder.VTYPE_MAP_CONNECTOR.equals(transportType)) { + return (T) decodeConnectorToObjectMap(targetType, + restrictToInternalTypes, (JSONObject) encodedJsonValue, + application); + } else if (JsonEncoder.VTYPE_MAP.equals(transportType)) { + return (T) decodeStringToObjectMap(targetType, + restrictToInternalTypes, (JSONObject) encodedJsonValue, + application); + } + + // Arrays + if (JsonEncoder.VTYPE_ARRAY.equals(transportType)) { + + return (T) decodeObjectArray(targetType, + (JSONArray) encodedJsonValue, application); + + } else if (JsonEncoder.VTYPE_STRINGARRAY.equals(transportType)) { + return (T) decodeStringArray((JSONArray) encodedJsonValue); + } + + // Special Vaadin types + + String stringValue = String.valueOf(encodedJsonValue); + + if (JsonEncoder.VTYPE_CONNECTOR.equals(transportType)) { + return (T) application.getConnector(stringValue); + } + + // Standard Java types + + if (JsonEncoder.VTYPE_STRING.equals(transportType)) { + return (T) stringValue; + } else if (JsonEncoder.VTYPE_INTEGER.equals(transportType)) { + return (T) Integer.valueOf(stringValue); + } else if (JsonEncoder.VTYPE_LONG.equals(transportType)) { + return (T) Long.valueOf(stringValue); + } else if (JsonEncoder.VTYPE_FLOAT.equals(transportType)) { + return (T) Float.valueOf(stringValue); + } else if (JsonEncoder.VTYPE_DOUBLE.equals(transportType)) { + return (T) Double.valueOf(stringValue); + } else if (JsonEncoder.VTYPE_BOOLEAN.equals(transportType)) { + return (T) Boolean.valueOf(stringValue); + } else if (JsonEncoder.VTYPE_NULL.equals(transportType)) { + return null; + } + + throw new JSONException("Unknown type " + transportType); + } + + @Deprecated + private static Map<String, Object> decodeStringToObjectMap(Type targetType, + boolean restrictToInternalTypes, JSONObject jsonMap, + Application application) throws JSONException { HashMap<String, Object> map = new HashMap<String, Object>(); Iterator<String> it = jsonMap.keys(); while (it.hasNext()) { String key = it.next(); - map.put(key, decode(jsonMap.getJSONArray(key), application)); + JSONArray encodedValueAndType = jsonMap.getJSONArray(key); + Object decodedChild; + if (!restrictToInternalTypes + && targetType instanceof ParameterizedType) { + Type mapValueType = ((ParameterizedType) targetType) + .getActualTypeArguments()[1]; + // Only decode the given type + decodedChild = decodeInternalOrCustomType(mapValueType, + encodedValueAndType, application); + } else { + // Only internal types when not enforcing a given type to avoid + // security issues + decodedChild = decodeInternalType(encodedValueAndType, + application); + } + map.put(key, decodedChild); } return map; } - private static Object decodeConnectorMap(JSONObject jsonMap, + @Deprecated + private static Object decodeConnectorToObjectMap(Type targetType, + boolean restrictToInternalTypes, JSONObject jsonMap, Application application) throws JSONException { HashMap<Connector, Object> map = new HashMap<Connector, Object>(); Iterator<String> it = jsonMap.keys(); while (it.hasNext()) { String connectorId = it.next(); Connector connector = application.getConnector(connectorId); - map.put(connector, - decode(jsonMap.getJSONArray(connectorId), application)); + JSONArray encodedValueAndType = jsonMap.getJSONArray(connectorId); + Object decodedChild; + if (!restrictToInternalTypes + && targetType instanceof ParameterizedType) { + Type mapValueType = ((ParameterizedType) targetType) + .getActualTypeArguments()[1]; + // Only decode the given type + decodedChild = decodeInternalOrCustomType(mapValueType, + encodedValueAndType, application); + } else { + // Only internal types when not enforcing a given type to avoid + // security issues + decodedChild = decodeInternalType(encodedValueAndType, + application); + } + map.put(connector, decodedChild); } return map; } @@ -161,43 +310,103 @@ public class JsonCodec implements Serializable { return tokens.toArray(new String[tokens.size()]); } - private static Object decodeArray(JSONArray jsonArray, - Application application) throws JSONException { - List list = decodeList(jsonArray, application); + private static Object[] decodeObjectArray(Type targetType, + JSONArray jsonArray, Application application) throws JSONException { + List list = decodeList(List.class, true, jsonArray, application); return list.toArray(new Object[list.size()]); } - private static List<Object> decodeList(JSONArray jsonArray, + private static List<Object> decodeList(Type targetType, + boolean restrictToInternalTypes, JSONArray jsonArray, Application application) throws JSONException { List<Object> list = new ArrayList<Object>(); for (int i = 0; i < jsonArray.length(); ++i) { // each entry always has two elements: type and value JSONArray entryArray = jsonArray.getJSONArray(i); - list.add(decode(entryArray, application)); + Object decodedChild; + if (!restrictToInternalTypes + && targetType instanceof ParameterizedType) { + // Only decode the given type + Type childType = ((ParameterizedType) targetType) + .getActualTypeArguments()[0]; + decodedChild = decodeInternalOrCustomType(childType, + entryArray, application); + } else { + // Only internal types when not enforcing a given type to avoid + // security issues + decodedChild = decodeInternalType(entryArray, application); + } + list.add(decodedChild); } return list; } - private static Set<Object> decodeSet(JSONArray jsonArray, + private static Set<Object> decodeSet(Type targetType, + boolean restrictToInternalTypes, JSONArray jsonArray, Application application) throws JSONException { HashSet<Object> set = new HashSet<Object>(); - set.addAll(decodeList(jsonArray, application)); + set.addAll(decodeList(List.class, restrictToInternalTypes, jsonArray, + application)); return set; } /** - * Encode a value to a JSON representation for transport from the server to - * the client. + * Returns the name that should be used as field name in the JSON. We strip + * "set" from the setter, keeping the result - this is easy to do on both + * server and client, avoiding some issues with cASE. E.g setZIndex() + * becomes "ZIndex". Also ensures that both getter and setter are present, + * returning null otherwise. * - * @param value - * value to convert - * @param application - * mapper between connector ID and {@link Connector} objects - * @return JSON representation of the value - * @throws JSONException - * if encoding a value fails (e.g. NaN or infinite number) + * @param pd + * @return the name to be used or null if both getter and setter are not + * found. */ - public static JSONArray encode(Object value, Application application) + private static String getTransportFieldName(PropertyDescriptor pd) { + if (pd.getReadMethod() == null || pd.getWriteMethod() == null) { + return null; + } + return pd.getWriteMethod().getName().substring(3); + } + + private static <T> T decodeObject(Type targetType, + JSONObject serializedObject, Application application) + throws JSONException { + + Class<?> targetClass = getClassForType(targetType); + try { + Object decodedObject = targetClass.newInstance(); + for (PropertyDescriptor pd : Introspector.getBeanInfo(targetClass) + .getPropertyDescriptors()) { + + String fieldName = getTransportFieldName(pd); + if (fieldName == null) { + continue; + } + JSONArray encodedFieldValue = serializedObject + .getJSONArray(fieldName); + Type fieldType = pd.getReadMethod().getGenericReturnType(); + Object decodedFieldValue = decodeInternalOrCustomType( + fieldType, encodedFieldValue, application); + + pd.getWriteMethod().invoke(decodedObject, decodedFieldValue); + } + + return (T) decodedObject; + } catch (IllegalArgumentException e) { + throw new JSONException(e); + } catch (IllegalAccessException e) { + throw new JSONException(e); + } catch (InvocationTargetException e) { + throw new JSONException(e); + } catch (InstantiationException e) { + throw new JSONException(e); + } catch (IntrospectionException e) { + throw new JSONException(e); + } + } + + @Deprecated + private static JSONArray encode(Object value, Application application) throws JSONException { return encode(value, null, application); } @@ -213,7 +422,7 @@ public class JsonCodec implements Serializable { valueType = value.getClass(); } - String transportType = getTransportType(valueType); + String internalTransportType = getInternalTransportType(valueType); if (value instanceof String[]) { String[] array = (String[]) value; JSONArray jsonArray = new JSONArray(); @@ -226,16 +435,16 @@ public class JsonCodec implements Serializable { } else if (value instanceof Boolean) { return combineTypeAndValue(JsonEncoder.VTYPE_BOOLEAN, value); } else if (value instanceof Number) { - return combineTypeAndValue(transportType, value); + return combineTypeAndValue(internalTransportType, value); } else if (value instanceof Collection) { - if (transportType == null) { + if (internalTransportType == null) { throw new RuntimeException( "Unable to serialize unsupported type: " + valueType); } Collection<?> collection = (Collection<?>) value; JSONArray jsonArray = encodeCollection(collection, application); - return combineTypeAndValue(transportType, jsonArray); + return combineTypeAndValue(internalTransportType, jsonArray); } else if (value instanceof Object[]) { Object[] array = (Object[]) value; JSONArray jsonArray = encodeArrayContents(array, application); @@ -260,12 +469,13 @@ public class JsonCodec implements Serializable { } return combineTypeAndValue(JsonEncoder.VTYPE_CONNECTOR, connector.getConnectorId()); - } else if (transportType != null) { - return combineTypeAndValue(transportType, String.valueOf(value)); + } else if (internalTransportType != null) { + return combineTypeAndValue(internalTransportType, + String.valueOf(value)); } else { // Any object that we do not know how to encode we encode by looping // through fields - return combineTypeAndValue(valueType.getName(), + return combineTypeAndValue(getCustomTransportType(valueType), encodeObject(value, application)); } } @@ -298,68 +508,11 @@ public class JsonCodec implements Serializable { return jsonMap; } - /** - * Returns the name that should be used as field name in the JSON. We strip - * "set" from the setter, keeping the result - this is easy to do on both - * server and client, avoiding some issues with cASE. E.g setZIndex() - * becomes "ZIndex". Also ensures that both getter and setter are present, - * returning null otherwise. - * - * @param pd - * @return the name to be used or null if both getter and setter are not - * found. - */ - private static String getTransportFieldName(PropertyDescriptor pd) { - if (pd.getReadMethod() == null || pd.getWriteMethod() == null) { - return null; - } - return pd.getWriteMethod().getName().substring(3); - } - - private static Object decodeObject(String type, - JSONObject serializedObject, Application application) - throws JSONException { - - Class<?> cls; - try { - cls = Class.forName(type); - - Object decodedObject = cls.newInstance(); - for (PropertyDescriptor pd : Introspector.getBeanInfo(cls) - .getPropertyDescriptors()) { - - String fieldName = getTransportFieldName(pd); - if (fieldName == null) { - continue; - } - JSONArray encodedObject = serializedObject - .getJSONArray(fieldName); - pd.getWriteMethod().invoke(decodedObject, - decode(encodedObject, application)); - } - - return decodedObject; - } catch (ClassNotFoundException e) { - throw new JSONException(e); - } catch (IllegalArgumentException e) { - throw new JSONException(e); - } catch (IllegalAccessException e) { - throw new JSONException(e); - } catch (InvocationTargetException e) { - throw new JSONException(e); - } catch (InstantiationException e) { - throw new JSONException(e); - } catch (IntrospectionException e) { - throw new JSONException(e); - } - } - private static JSONArray encodeArrayContents(Object[] array, Application application) throws JSONException { JSONArray jsonArray = new JSONArray(); for (Object o : array) { - // TODO handle object graph loops? - jsonArray.put(encode(o, application)); + jsonArray.put(encode(o, null, application)); } return jsonArray; } @@ -368,7 +521,6 @@ public class JsonCodec implements Serializable { Application application) throws JSONException { JSONArray jsonArray = new JSONArray(); for (Object o : collection) { - // TODO handle object graph loops? jsonArray.put(encode(o, application)); } return jsonArray; @@ -388,7 +540,7 @@ public class JsonCodec implements Serializable { "Only maps with String/Connector keys are currently supported (#8602)"); } - jsonMap.put((String) mapKey, encode(mapValue, application)); + jsonMap.put((String) mapKey, encode(mapValue, null, application)); } return jsonMap; } @@ -405,21 +557,6 @@ public class JsonCodec implements Serializable { } /** - * Gets the transport type for the value. Returns null if no transport type - * can be found. - * - * @param value - * @return - * @throws JSONException - */ - private static String getTransportType(Object value) { - if (null == value) { - return JsonEncoder.VTYPE_NULL; - } - return getTransportType(value.getClass()); - } - - /** * Gets the transport type for the given class. Returns null if no transport * type can be found. * @@ -428,9 +565,12 @@ public class JsonCodec implements Serializable { * @return * @throws JSONException */ - private static String getTransportType(Class<?> valueType) { + private static String getInternalTransportType(Type valueType) { return typeToTransportType.get(valueType); + } + private static String getCustomTransportType(Class<?> targetType) { + return targetType.getName(); } } |