diff --git a/src/main/java/net/imagej/server/ImageJServer.java b/src/main/java/net/imagej/server/ImageJServer.java index 32284934..9d65780f 100644 --- a/src/main/java/net/imagej/server/ImageJServer.java +++ b/src/main/java/net/imagej/server/ImageJServer.java @@ -49,22 +49,20 @@ * * @author Leon Yang */ -public class ImageJServer extends - Application -{ +public class ImageJServer extends Application { private final Context ctx; private final ObjectService objectService; private final JsonService jsonService; - + private Environment env; public ImageJServer(final Context ctx) { this.ctx = ctx; objectService = new DefaultObjectService(); - jsonService = new DefaultJsonService(objectService); + jsonService = new DefaultJsonService(ctx, objectService); } @Override diff --git a/src/main/java/net/imagej/server/json/JsonSerializerAdapter.java b/src/main/java/net/imagej/server/json/JsonSerializerAdapter.java new file mode 100644 index 00000000..804599e4 --- /dev/null +++ b/src/main/java/net/imagej/server/json/JsonSerializerAdapter.java @@ -0,0 +1,29 @@ +package net.imagej.server.json; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +import java.io.IOException; + +class JsonSerializerAdapter extends StdSerializer { + + private SciJavaJsonSerializer serializedDelegate; + + public JsonSerializerAdapter( + SciJavaJsonSerializer serializedDelegate) + { + super(serializedDelegate.handleType()); + this.serializedDelegate = serializedDelegate; + } + + + @Override + public void serialize(T value, JsonGenerator gen, + SerializerProvider provider) throws IOException + { + serializedDelegate.serialize(value, gen, provider); + } + + +} \ No newline at end of file diff --git a/src/main/java/net/imagej/server/json/SciJavaJsonSerializer.java b/src/main/java/net/imagej/server/json/SciJavaJsonSerializer.java new file mode 100644 index 00000000..28e413d0 --- /dev/null +++ b/src/main/java/net/imagej/server/json/SciJavaJsonSerializer.java @@ -0,0 +1,32 @@ + +package net.imagej.server.json; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.module.SimpleModule; + +import java.io.IOException; + +import org.scijava.plugin.SciJavaPlugin; + +public interface SciJavaJsonSerializer extends SciJavaPlugin + +{ + + void serialize(T value, JsonGenerator gen, + SerializerProvider serializers) + throws IOException; + + Class handleType(); + + default boolean isSupportedBy(Class desiredClass) { + return handleType().isAssignableFrom(desiredClass); + } + + default public void register(ObjectMapper mapper) { + SimpleModule mod = new SimpleModule(); + mod.addSerializer(new JsonSerializerAdapter<>(this)); + mapper.registerModule(mod); + } +} diff --git a/src/main/java/net/imagej/server/services/DefaultJsonService.java b/src/main/java/net/imagej/server/services/DefaultJsonService.java index 9f2f3285..e2c2c466 100644 --- a/src/main/java/net/imagej/server/services/DefaultJsonService.java +++ b/src/main/java/net/imagej/server/services/DefaultJsonService.java @@ -47,10 +47,15 @@ import java.io.IOException; import java.util.Arrays; +import java.util.List; +import net.imagej.server.json.SciJavaJsonSerializer; import net.imagej.server.mixins.Mixins; import net.imglib2.EuclideanSpace; +import org.scijava.Context; +import org.scijava.plugin.PluginService; + /** * Service that handle customized JSON serialization and deserialization. * @@ -73,13 +78,16 @@ public class DefaultJsonService implements JsonService { */ private final UntypedObjectDeserializer idToObjDeserializer; + private final List jsonSerializers; + /** * Constructs and initializes a JsonService with an {@link ObjectService}. * * @param objectService */ - public DefaultJsonService(final ObjectService objectService) { - + public DefaultJsonService(final Context ctx, + final ObjectService objectService) + { idToObjDeserializer = new UntypedObjectDeserializer(null, null) { @Override @@ -101,7 +109,7 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) final JsonSerializer objToIdSerializer = new JsonSerializer() - { + { @Override public void serialize(Object value, JsonGenerator gen, @@ -120,13 +128,24 @@ public void serialize(Object value, JsonGenerator gen, public JsonSerializer modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer serializer) { - if (Mixins.support(beanDesc.getBeanClass())) return serializer; + final Class desiredClass = beanDesc.getBeanClass(); + + // If the serialized class is supported by mixins, let's go for one + if (Mixins.support(desiredClass)) return serializer; + + // If the serialized class is supported thanks to a modification to + // ObjectMapper, let's do it that way + if (jsonSerializers.stream().map(obj -> (SciJavaJsonSerializer) obj) + .anyMatch(e -> e.isSupportedBy( + desiredClass))) return serializer; + // If the serialized class is unknown (i.e. serialized using the general // BeanSerializer) or should not be serialized (i.e. complicated class // implemented interfaces such as Iterable), would be serialized as an // ID. if (serializer instanceof BeanSerializer) return objToIdSerializer; - if (notSerialized(beanDesc.getBeanClass())) return objToIdSerializer; + if (notSerialized(desiredClass)) return objToIdSerializer; + return serializer; } @@ -134,6 +153,11 @@ public JsonSerializer modifySerializer(SerializationConfig config, objToIdMapper = new ObjectMapper(); objToIdMapper.registerModule(objToIdModule); + jsonSerializers = ctx.getService(PluginService.class).createInstancesOfType( + SciJavaJsonSerializer.class); + + registerSerializers(); + // register Jackson MixIns to obtain better json output format for some // specific types Mixins.registerMixIns(objToIdMapper); @@ -156,4 +180,8 @@ public boolean notSerialized(final Class target) { .isAssignableFrom(target)); } + private void registerSerializers() { + jsonSerializers.stream().map(obj -> (SciJavaJsonSerializer) obj).forEach( + serializer -> serializer.register(objToIdMapper)); + } } diff --git a/src/test/java/net/imagej/server/AbstractResourceTest.java b/src/test/java/net/imagej/server/AbstractResourceTest.java index 7f2429e9..28cbf03e 100644 --- a/src/test/java/net/imagej/server/AbstractResourceTest.java +++ b/src/test/java/net/imagej/server/AbstractResourceTest.java @@ -46,7 +46,7 @@ public abstract class AbstractResourceTest { protected static final ObjectService objectService = new DefaultObjectService(); - protected static final JsonService jsonService = new DefaultJsonService( + protected static final JsonService jsonService = new DefaultJsonService(ctx, objectService); protected static final ObjectMapper objectMapper = new ObjectMapper(); diff --git a/src/test/java/net/imagej/server/DefaultJsonServiceTest.java b/src/test/java/net/imagej/server/DefaultJsonServiceTest.java index 20c3482a..31756ad2 100644 --- a/src/test/java/net/imagej/server/DefaultJsonServiceTest.java +++ b/src/test/java/net/imagej/server/DefaultJsonServiceTest.java @@ -54,6 +54,7 @@ import org.junit.Before; import org.junit.Test; +import org.scijava.Context; /** * Test deserialization and serialization using DefaultJsonService. @@ -66,12 +67,13 @@ public class DefaultJsonServiceTest { private ObjectMapper modifiedMapper; private ListObjectService objectService; private DefaultJsonService jsonService; + protected static final Context ctx = new Context(); @Before public void setup() { modifiedMapper = Jackson.newObjectMapper(); objectService = new ListObjectService(); - jsonService = new DefaultJsonService(objectService); + jsonService = new DefaultJsonService(ctx, objectService); jsonService.addDeserializerTo(modifiedMapper); } diff --git a/src/test/java/net/imagej/server/SciJavaJsonSerializerTest.java b/src/test/java/net/imagej/server/SciJavaJsonSerializerTest.java new file mode 100644 index 00000000..383cd579 --- /dev/null +++ b/src/test/java/net/imagej/server/SciJavaJsonSerializerTest.java @@ -0,0 +1,89 @@ + +package net.imagej.server; + +import static io.dropwizard.testing.FixtureHelpers.fixture; +import static org.junit.Assert.assertEquals; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; + +import io.dropwizard.jackson.Jackson; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import net.imagej.server.json.SciJavaJsonSerializer; +import net.imagej.server.services.DefaultJsonService; +import net.imagej.server.services.DefaultObjectService; +import net.imglib2.FinalInterval; +import net.imglib2.Interval; + +import org.junit.Test; +import org.scijava.Context; +import org.scijava.plugin.Plugin; + +public class SciJavaJsonSerializerTest { + + @Test + public void serializeIntervalUsingSciJavaJsonSerializer() throws Exception { + + final ObjectMapper mapper = Jackson.newObjectMapper(); + final DefaultObjectService objectService = new DefaultObjectService(); + final DefaultJsonService jsonService = new DefaultJsonService(new Context(), + objectService); + jsonService.addDeserializerTo(mapper); + + final Interval testInterval = new FinalInterval(new long[] { 0, 1 }, + new long[] { 8, 9 }); + final Map outputs = new HashMap<>(); + outputs.put("testInterval", testInterval); + + final String parsed = jsonService.parseObject(outputs); + + final String expected = mapper.writeValueAsString(mapper.readValue(fixture( + "fixtures/outputs/finalIntervalType.json"), Map.class)); + + assertEquals(expected, parsed); + + } + + @Plugin(type = SciJavaJsonSerializer.class) + public static class ExampleIntervalJsonSerializer implements + SciJavaJsonSerializer + { + + @Override + public boolean isSupportedBy(Class desiredClass) { + return desiredClass.equals(FinalInterval.class); + } + + @Override + public void serialize(Interval interval, JsonGenerator gen, + SerializerProvider serializers) throws IOException + { + + gen.writeStartObject(); + gen.writeArrayFieldStart("min"); + for (int i = 0; i < interval.numDimensions(); i++) { + gen.writeNumber(interval.min(i)); + } + gen.writeEndArray(); + gen.writeArrayFieldStart("max"); + for (int i = 0; i < interval.numDimensions(); i++) { + gen.writeNumber(interval.max(i)); + } + gen.writeEndArray(); + gen.writeEndObject(); + + } + + @Override + public Class handleType() { + return Interval.class; + } + + } + +} diff --git a/src/test/resources/fixtures/outputs/finalIntervalType.json b/src/test/resources/fixtures/outputs/finalIntervalType.json new file mode 100644 index 00000000..55594752 --- /dev/null +++ b/src/test/resources/fixtures/outputs/finalIntervalType.json @@ -0,0 +1,6 @@ +{ + "testInterval": { + "min": [0, 1], + "max": [8, 9] + } +} \ No newline at end of file