parallel structure for JSonSerializer/DeserializerWithAccessFilter

This commit is contained in:
Michael Hoennig 2019-04-23 06:57:06 +02:00
parent 90316a262b
commit bb0fb4aa78
4 changed files with 46 additions and 21 deletions

View File

@ -20,9 +20,10 @@ public class JSonDeserializerWithAccessFilter<T> {
public JSonDeserializerWithAccessFilter(final JsonParser jsonParser, final DeserializationContext deserializationContext, Class<T> dtoClass) { public JSonDeserializerWithAccessFilter(final JsonParser jsonParser, final DeserializationContext deserializationContext, Class<T> dtoClass) {
this.treeNode = unchecked(() -> jsonParser.getCodec().readTree(jsonParser)); 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() { public T deserialize() {
treeNode.fieldNames().forEachRemaining(fieldName -> { treeNode.fieldNames().forEachRemaining(fieldName -> {
try { try {

View File

@ -13,21 +13,26 @@ import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@JsonComponent public class JSonSerializerWithAccessFilter <T> {
public class JSonSerializerWithAccessFilter extends JsonSerializer<Object> { private final JsonGenerator jsonGenerator;
private final SerializerProvider serializerProvider;
private final T dto;
@Override public JSonSerializerWithAccessFilter(final JsonGenerator jsonGenerator,
public void serialize(final Object dto, final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider,
final SerializerProvider serializerProvider) throws IOException { 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(); jsonGenerator.writeStartObject();
for (Field prop : dto.getClass().getDeclaredFields()) { for (Field prop : dto.getClass().getDeclaredFields()) {
toJSon(dto, jsonGenerator, prop); toJSon(dto, jsonGenerator, prop);
} }
jsonGenerator.writeEndObject(); jsonGenerator.writeEndObject();
} }
@ -35,9 +40,10 @@ public class JSonSerializerWithAccessFilter extends JsonSerializer<Object> {
if (getLoginUserRole().isAllowedToRead(prop)) { if (getLoginUserRole().isAllowedToRead(prop)) {
final String fieldName = prop.getName(); final String fieldName = prop.getName();
// TODO: maybe replace by serializerProvider.defaultSerialize...()? // 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 // 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. // 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())) { if (Integer.class.isAssignableFrom(prop.getType()) || int.class.isAssignableFrom(prop.getType())) {
jsonGenerator.writeNumberField(fieldName, (int) get(dto, prop)); jsonGenerator.writeNumberField(fieldName, (int) get(dto, prop));
} else if (Long.class.isAssignableFrom(prop.getType()) || long.class.isAssignableFrom(prop.getType())) { } else if (Long.class.isAssignableFrom(prop.getType()) || long.class.isAssignableFrom(prop.getType())) {

View File

@ -1,13 +1,17 @@
package org.hostsharing.hsadminng.service.dto; package org.hostsharing.hsadminng.service.dto;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.TreeNode; import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer; 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.IntNode;
import com.fasterxml.jackson.databind.node.TextNode; import com.fasterxml.jackson.databind.node.TextNode;
import org.hostsharing.hsadminng.service.accessfilter.AccessFor; import org.hostsharing.hsadminng.service.accessfilter.AccessFor;
import org.hostsharing.hsadminng.service.accessfilter.JSonDeserializerWithAccessFilter; import org.hostsharing.hsadminng.service.accessfilter.JSonDeserializerWithAccessFilter;
import org.hostsharing.hsadminng.service.accessfilter.JSonSerializerWithAccessFilter;
import org.hostsharing.hsadminng.service.accessfilter.Role; import org.hostsharing.hsadminng.service.accessfilter.Role;
import org.springframework.boot.jackson.JsonComponent; import org.springframework.boot.jackson.JsonComponent;
@ -171,13 +175,26 @@ public class CustomerDTO implements Serializable {
} }
@JsonComponent @JsonComponent
public static class UserJsonDeserializer extends JsonDeserializer<CustomerDTO> { public static class CustomerJsonSerializer extends JsonSerializer<CustomerDTO> {
@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<CustomerDTO> {
@Override @Override
public CustomerDTO deserialize(final JsonParser jsonParser, public CustomerDTO deserialize(final JsonParser jsonParser,
final DeserializationContext deserializationContext) { final DeserializationContext deserializationContext) {
return new JSonDeserializerWithAccessFilter<>(jsonParser, deserializationContext, CustomerDTO.class).deserialize(); return new JSonDeserializerWithAccessFilter<>(jsonParser, deserializationContext, CustomerDTO.class).deserialize();
} }
} }
} }

View File

@ -37,7 +37,7 @@ public class JSonSerializerWithAccessFilterUnitTest {
@Test @Test
public void shouldSerializeStringField() throws IOException { public void shouldSerializeStringField() throws IOException {
// when // when
new JSonSerializerWithAccessFilter().serialize(givenDTO, jsonGenerator, null); new JSonSerializerWithAccessFilter<>(jsonGenerator, null, givenDTO).serialize();
// then // then
verify(jsonGenerator).writeStringField("openStringField", givenDTO.openStringField); verify(jsonGenerator).writeStringField("openStringField", givenDTO.openStringField);
@ -50,10 +50,10 @@ public class JSonSerializerWithAccessFilterUnitTest {
givenLoginUserWithRole(Role.FINANCIAL_CONTACT); givenLoginUserWithRole(Role.FINANCIAL_CONTACT);
// when // when
new JSonSerializerWithAccessFilter().serialize(givenDTO, jsonGenerator, null); new JSonSerializerWithAccessFilter<>(jsonGenerator, null, givenDTO).serialize();
// then // then
verify(jsonGenerator).writeStringField("restrictedField", givenDTO.restrictedField); verify(jsonGenerator).writeStringField("restrictedField", givenDTO.restrictedField);
} }
@Test @Test
@ -63,25 +63,26 @@ public class JSonSerializerWithAccessFilterUnitTest {
givenLoginUserWithRole(Role.ANY_CUSTOMER_USER); givenLoginUserWithRole(Role.ANY_CUSTOMER_USER);
// when // when
new JSonSerializerWithAccessFilter().serialize(givenDTO, jsonGenerator, null); new JSonSerializerWithAccessFilter<>(jsonGenerator, null, givenDTO).serialize();
// then // then
verify(jsonGenerator, never()).writeStringField("restrictedField", givenDTO.restrictedField); verify(jsonGenerator, never()).writeStringField("restrictedField", givenDTO.restrictedField);
} }
@Test @Test
public void shouldThrowExceptionForUnimplementedFieldType() throws IOException { public void shouldThrowExceptionForUnimplementedFieldType() {
// given // given
class Arbitrary {} class Arbitrary {
}
class GivenDtoWithUnimplementedFieldType { class GivenDtoWithUnimplementedFieldType {
@AccessFor(read = Role.ANYBODY) @AccessFor(read = Role.ANYBODY)
Arbitrary fieldWithUnimplementedType; Arbitrary fieldWithUnimplementedType;
} }
final GivenDtoWithUnimplementedFieldType givenDto = new GivenDtoWithUnimplementedFieldType(); final GivenDtoWithUnimplementedFieldType givenDtoWithUnimplementedFieldType = new GivenDtoWithUnimplementedFieldType();
// when // when
Throwable actual = catchThrowable(() -> new JSonSerializerWithAccessFilter().serialize(givenDto, jsonGenerator, null)); Throwable actual = catchThrowable(() -> new JSonSerializerWithAccessFilter<>(jsonGenerator, null, givenDtoWithUnimplementedFieldType).serialize());
// then // then
assertThat(actual).isInstanceOf(NotImplementedException.class); assertThat(actual).isInstanceOf(NotImplementedException.class);