From 9d5bdaf89bd173bec7364d591377ca17bb5bb91d Mon Sep 17 00:00:00 2001 From: =?utf8?q?Olli=20Tiet=C3=A4v=C3=A4inen?= Date: Fri, 22 Feb 2019 10:50:51 +0200 Subject: [PATCH] Custom serializers accessors (#10658) * add accessor methods for CUSTOM_SERIALIZERS in JsonCodec * javadoc * removed removeCustomSerializer method, renamed putCustomSerializer to addCustomSerializer, added sanity checks and JavaDocs * refactored addCustomJsonSerializer to set, added test UI * move enums to be parsed after custom serializers * move adding custom serializer to static block * throw an exception if multiple serializers are registered for class * updated javadocs * changed CustomJSONSerializerTest to a SingleBrowserTest * moved CustomJSONSerializerTest to server/ and it's now not a browser test * removed CustomJSONSerializerTest --- .../java/com/vaadin/server/JsonCodec.java | 65 +++++++++++++++++-- .../server/CustomJSONSerializerTest.java | 52 +++++++++++++++ .../application/CustomJSONSerializer.java | 63 ++++++++++++++++++ 3 files changed, 174 insertions(+), 6 deletions(-) create mode 100644 server/src/test/java/com/vaadin/server/CustomJSONSerializerTest.java create mode 100644 uitest/src/main/java/com/vaadin/tests/application/CustomJSONSerializer.java diff --git a/server/src/main/java/com/vaadin/server/JsonCodec.java b/server/src/main/java/com/vaadin/server/JsonCodec.java index eeb27b0e5a..877fc6192f 100644 --- a/server/src/main/java/com/vaadin/server/JsonCodec.java +++ b/server/src/main/java/com/vaadin/server/JsonCodec.java @@ -325,14 +325,14 @@ public class JsonCodec implements Serializable { } else if (JsonValue.class .isAssignableFrom(getClassForType(targetType))) { return value; - } else if (Enum.class.isAssignableFrom(getClassForType(targetType))) { - Class classForType = getClassForType(targetType); - return decodeEnum(classForType.asSubclass(Enum.class), - (JsonString) value); } else if (CUSTOM_SERIALIZERS .containsKey(getClassForType(targetType))) { return CUSTOM_SERIALIZERS.get(getClassForType(targetType)) .deserialize(targetType, value, connectorTracker); + } else if (Enum.class.isAssignableFrom(getClassForType(targetType))) { + Class classForType = getClassForType(targetType); + return decodeEnum(classForType.asSubclass(Enum.class), + (JsonString) value); } else { return decodeObject(targetType, (JsonObject) value, connectorTracker); @@ -445,6 +445,59 @@ public class JsonCodec implements Serializable { throw new JsonException("Unknown type " + transportType); } + /** + * Set a custom JSONSerializer for a specific Class. Existence of custom + * serializers is checked after basic types (Strings, Booleans, Numbers, + * Characters), Collections and Maps, so setting custom serializers for + * these won't have any effect. + *

+ * To remove a previously set serializer, call this method with the second + * parameter set to {@code null}. + *

+ * Custom serializers should only be added from static initializers or other + * places that are guaranteed to run only once. Trying to add a serializer + * to a class that already has one will cause an exception. + *

+ * Warning: removing existing custom serializers may lead into unexpected + * behavior in components that expect the customized data. The framework's + * custom serializers are loaded in the static initializer block of this + * class. + * + * @see DateSerializer + * @throws IllegalArgumentException + * Thrown if parameter clazz is null. + * @throws IllegalStateException + * Thrown if serializer for parameter clazz is already + * registered and parameter jsonSerializer is not null. + * @param clazz + * The target class. + * @param jsonSerializer + * Custom JSONSerializer to add. If {@code null}, remove custom + * serializer from class clazz. + */ + public static void setCustomSerializer(Class clazz, + JSONSerializer jsonSerializer) { + if (clazz == null) { + throw new IllegalArgumentException( + "Cannot add serializer for null"); + } + if (jsonSerializer == null) { + CUSTOM_SERIALIZERS.remove(clazz); + } else { + if (CUSTOM_SERIALIZERS.containsKey(clazz)) { + String err = String.format( + "Class %s already has a custom serializer. " + + "This exception can be thrown if you try to " + + "add a serializer from a non-static context. " + + "Try using a static block instead.", + clazz.getName()); + throw new IllegalStateException(err); + } + CUSTOM_SERIALIZERS.put(clazz, jsonSerializer); + } + + } + private static UidlValue decodeUidlValue(JsonArray encodedJsonValue, ConnectorTracker connectorTracker) { String type = encodedJsonValue.getString(0); @@ -668,10 +721,10 @@ public class JsonCodec implements Serializable { } // Connectors are simply serialized as ID. toReturn = Json.create(((Connector) value).getConnectorId()); - } else if (value instanceof Enum) { - toReturn = Json.create(((Enum) value).name()); } else if (CUSTOM_SERIALIZERS.containsKey(value.getClass())) { toReturn = serializeJson(value, connectorTracker); + } else if (value instanceof Enum) { + toReturn = Json.create(((Enum) value).name()); } else if (valueType instanceof GenericArrayType) { toReturn = encodeArrayContents( ((GenericArrayType) valueType).getGenericComponentType(), diff --git a/server/src/test/java/com/vaadin/server/CustomJSONSerializerTest.java b/server/src/test/java/com/vaadin/server/CustomJSONSerializerTest.java new file mode 100644 index 0000000000..0c8b69fe62 --- /dev/null +++ b/server/src/test/java/com/vaadin/server/CustomJSONSerializerTest.java @@ -0,0 +1,52 @@ +package com.vaadin.server; + +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.Type; + +import org.junit.Test; + +import com.vaadin.server.communication.JSONSerializer; +import com.vaadin.ui.ConnectorTracker; + +import elemental.json.JsonValue; + +public class CustomJSONSerializerTest { + + public static class Foo { + + } + + public static class FooSerializer implements JSONSerializer { + + @Override + public Foo deserialize(Type type, JsonValue jsonValue, + ConnectorTracker connectorTracker) { + return null; + } + + @Override + public JsonValue serialize(Foo value, + ConnectorTracker connectorTracker) { + return null; + } + + } + + @Test + public void testMultipleRegistration() { + boolean thrown = false; + try { + JsonCodec.setCustomSerializer(Foo.class, new FooSerializer()); + JsonCodec.setCustomSerializer(Foo.class, new FooSerializer()); + } catch (IllegalStateException ise) { + thrown = true; + } finally { + JsonCodec.setCustomSerializer(Foo.class, null); + } + assertTrue("Multiple serializer registrations for one class " + + "should throw an IllegalStateException", thrown); + + } + +} diff --git a/uitest/src/main/java/com/vaadin/tests/application/CustomJSONSerializer.java b/uitest/src/main/java/com/vaadin/tests/application/CustomJSONSerializer.java new file mode 100644 index 0000000000..9c8ca554d8 --- /dev/null +++ b/uitest/src/main/java/com/vaadin/tests/application/CustomJSONSerializer.java @@ -0,0 +1,63 @@ +package com.vaadin.tests.application; + +import java.lang.reflect.Type; + +import com.vaadin.server.JsonCodec; +import com.vaadin.server.VaadinRequest; +import com.vaadin.server.communication.JSONSerializer; +import com.vaadin.shared.communication.URLReference; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.Button; +import com.vaadin.ui.ConnectorTracker; +import com.vaadin.ui.LoginForm; + +import elemental.json.Json; +import elemental.json.JsonObject; +import elemental.json.JsonValue; + +public class CustomJSONSerializer extends AbstractTestUI { + + static { + JsonCodec.setCustomSerializer(URLReference.class, + new JSONSerializer() { + + @Override + public URLReference deserialize(Type type, + JsonValue jsonValue, + ConnectorTracker connectorTracker) { + // NOP + return null; + } + + @Override + public JsonValue serialize(URLReference value, + ConnectorTracker connectorTracker) { + JsonObject result = Json.createObject(); + String url = value.getURL(); + // change all test.com urls to vaadin.com + if ("http://www.test.com".equals(url)) { + url = "http://www.vaadin.com"; + } + result.put("uRL", url); + return result; + } + + }); + } + + public static class MyLoginForm extends LoginForm { + public void setResource(URLReference ref) { + getState().loginResource = ref; + } + } + + @Override + protected void setup(VaadinRequest request) { + MyLoginForm loginForm = new MyLoginForm(); + URLReference url = new URLReference(); + url.setURL("http://www.test.com"); + loginForm.setResource(url); + addComponent(loginForm); + } + +} -- 2.39.5