From bb0fb4aa7880515c7d1c3f54bb4e79f3a30def01 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Tue, 23 Apr 2019 06:57:06 +0200 Subject: [PATCH] parallel structure for JSonSerializer/DeserializerWithAccessFilter --- .../JSonDeserializerWithAccessFilter.java | 3 ++- .../JSonSerializerWithAccessFilter.java | 26 ++++++++++++------- .../hsadminng/service/dto/CustomerDTO.java | 21 +++++++++++++-- ...SonSerializerWithAccessFilterUnitTest.java | 17 ++++++------ 4 files changed, 46 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilter.java b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilter.java index 40d02455..e9c25ae9 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilter.java +++ b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilter.java @@ -20,9 +20,10 @@ public class JSonDeserializerWithAccessFilter { public JSonDeserializerWithAccessFilter(final JsonParser jsonParser, final DeserializationContext deserializationContext, Class dtoClass) { this.treeNode = unchecked(() -> jsonParser.getCodec().readTree(jsonParser)); - this.dto = unchecked(() -> dtoClass.newInstance()); + this.dto = unchecked(dtoClass::newInstance); } + // Jackson deserializes from the JsonParser, thus no input parameter needed. public T deserialize() { treeNode.fieldNames().forEachRemaining(fieldName -> { try { diff --git a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilter.java b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilter.java index 18770a28..c4d1986a 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilter.java +++ b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilter.java @@ -13,21 +13,26 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -@JsonComponent -public class JSonSerializerWithAccessFilter extends JsonSerializer { +public class JSonSerializerWithAccessFilter { + private final JsonGenerator jsonGenerator; + private final SerializerProvider serializerProvider; + private final T dto; - @Override - public void serialize(final Object dto, final JsonGenerator jsonGenerator, - final SerializerProvider serializerProvider) throws IOException { + public JSonSerializerWithAccessFilter(final JsonGenerator jsonGenerator, + final SerializerProvider serializerProvider, + final T dto) { + this.jsonGenerator = jsonGenerator; + this.serializerProvider = serializerProvider; + this.dto = dto; + } + + // Jackson serializes into the JsonGenerator, thus no return value needed. + public void serialize() throws IOException { - // TODO: Move the implementation to an (if necessary, inner) class, or maybe better - // expose just the inner implementation from an explicit @JsonCompontent - // as it's necessary for the deserializers anyway. jsonGenerator.writeStartObject(); for (Field prop : dto.getClass().getDeclaredFields()) { toJSon(dto, jsonGenerator, prop); } - jsonGenerator.writeEndObject(); } @@ -35,9 +40,10 @@ public class JSonSerializerWithAccessFilter extends JsonSerializer { if (getLoginUserRole().isAllowedToRead(prop)) { final String fieldName = prop.getName(); // TODO: maybe replace by serializerProvider.defaultSerialize...()? - // But that's difficult for parallel structure with the deserializer, where the API is ugly. + // But that makes it difficult for parallel structure with the deserializer (clumsy API). // Alternatively extract the supported types to subclasses of some abstract class and // here as well as in the deserializer just access the matching implementation through a map. + // Or even completely switch from Jackson to GSON? if (Integer.class.isAssignableFrom(prop.getType()) || int.class.isAssignableFrom(prop.getType())) { jsonGenerator.writeNumberField(fieldName, (int) get(dto, prop)); } else if (Long.class.isAssignableFrom(prop.getType()) || long.class.isAssignableFrom(prop.getType())) { diff --git a/src/main/java/org/hostsharing/hsadminng/service/dto/CustomerDTO.java b/src/main/java/org/hostsharing/hsadminng/service/dto/CustomerDTO.java index f789c4be..cdec25f3 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/dto/CustomerDTO.java +++ b/src/main/java/org/hostsharing/hsadminng/service/dto/CustomerDTO.java @@ -1,13 +1,17 @@ package org.hostsharing.hsadminng.service.dto; +import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.TreeNode; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.node.IntNode; import com.fasterxml.jackson.databind.node.TextNode; import org.hostsharing.hsadminng.service.accessfilter.AccessFor; import org.hostsharing.hsadminng.service.accessfilter.JSonDeserializerWithAccessFilter; +import org.hostsharing.hsadminng.service.accessfilter.JSonSerializerWithAccessFilter; import org.hostsharing.hsadminng.service.accessfilter.Role; import org.springframework.boot.jackson.JsonComponent; @@ -171,13 +175,26 @@ public class CustomerDTO implements Serializable { } @JsonComponent - public static class UserJsonDeserializer extends JsonDeserializer { + public static class CustomerJsonSerializer extends JsonSerializer { + + @Override + public void serialize(final CustomerDTO customerDTO, final JsonGenerator jsonGenerator, + final SerializerProvider serializerProvider) throws IOException { + + new JSonSerializerWithAccessFilter<>(jsonGenerator, serializerProvider, customerDTO).serialize(); + } + } + + + + @JsonComponent + public static class CustomerJsonDeserializer extends JsonDeserializer { @Override public CustomerDTO deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) { - return new JSonDeserializerWithAccessFilter<>(jsonParser, deserializationContext, CustomerDTO.class).deserialize(); + return new JSonDeserializerWithAccessFilter<>(jsonParser, deserializationContext, CustomerDTO.class).deserialize(); } } } diff --git a/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilterUnitTest.java b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilterUnitTest.java index 2e7be2fb..432a53ad 100644 --- a/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilterUnitTest.java +++ b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilterUnitTest.java @@ -37,7 +37,7 @@ public class JSonSerializerWithAccessFilterUnitTest { @Test public void shouldSerializeStringField() throws IOException { // when - new JSonSerializerWithAccessFilter().serialize(givenDTO, jsonGenerator, null); + new JSonSerializerWithAccessFilter<>(jsonGenerator, null, givenDTO).serialize(); // then verify(jsonGenerator).writeStringField("openStringField", givenDTO.openStringField); @@ -50,10 +50,10 @@ public class JSonSerializerWithAccessFilterUnitTest { givenLoginUserWithRole(Role.FINANCIAL_CONTACT); // when - new JSonSerializerWithAccessFilter().serialize(givenDTO, jsonGenerator, null); + new JSonSerializerWithAccessFilter<>(jsonGenerator, null, givenDTO).serialize(); // then - verify(jsonGenerator).writeStringField("restrictedField", givenDTO.restrictedField); + verify(jsonGenerator).writeStringField("restrictedField", givenDTO.restrictedField); } @Test @@ -63,25 +63,26 @@ public class JSonSerializerWithAccessFilterUnitTest { givenLoginUserWithRole(Role.ANY_CUSTOMER_USER); // when - new JSonSerializerWithAccessFilter().serialize(givenDTO, jsonGenerator, null); + new JSonSerializerWithAccessFilter<>(jsonGenerator, null, givenDTO).serialize(); // then verify(jsonGenerator, never()).writeStringField("restrictedField", givenDTO.restrictedField); } @Test - public void shouldThrowExceptionForUnimplementedFieldType() throws IOException { + public void shouldThrowExceptionForUnimplementedFieldType() { // given - class Arbitrary {} + class Arbitrary { + } class GivenDtoWithUnimplementedFieldType { @AccessFor(read = Role.ANYBODY) Arbitrary fieldWithUnimplementedType; } - final GivenDtoWithUnimplementedFieldType givenDto = new GivenDtoWithUnimplementedFieldType(); + final GivenDtoWithUnimplementedFieldType givenDtoWithUnimplementedFieldType = new GivenDtoWithUnimplementedFieldType(); // when - Throwable actual = catchThrowable(() -> new JSonSerializerWithAccessFilter().serialize(givenDto, jsonGenerator, null)); + Throwable actual = catchThrowable(() -> new JSonSerializerWithAccessFilter<>(jsonGenerator, null, givenDtoWithUnimplementedFieldType).serialize()); // then assertThat(actual).isInstanceOf(NotImplementedException.class);