From bc87739d6fa0c3607ded93a765fab332bc1149d6 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Thu, 18 Apr 2019 14:48:58 +0200 Subject: [PATCH] JSON serialiezr/deserializer for CustomerDTO - manually --- .../hsadminng/service/dto/CustomerDTO.java | 171 +++++++++++++++++- .../service/dto/CustomerDTOUnitTest.java | 113 ++++++++++++ 2 files changed, 276 insertions(+), 8 deletions(-) create mode 100644 src/test/java/org/hostsharing/hsadminng/service/dto/CustomerDTOUnitTest.java 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 1e98bfec..ce713409 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,41 @@ package org.hostsharing.hsadminng.service.dto; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +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.apache.commons.lang3.NotImplementedException; +import org.hostsharing.hsadminng.security.SecurityUtils; +import org.springframework.boot.jackson.JsonComponent; + +import javax.annotation.PostConstruct; import javax.validation.constraints.*; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.io.IOException; import java.io.Serializable; +import java.lang.annotation.*; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.Objects; +import java.util.Optional; + /** * A DTO for the Customer entity. */ +@ReadableFor(Role.ANY_CUSTOMER_USER) +@WritableFor(Role.SUPPORTER) public class CustomerDTO implements Serializable { + @WritableFor(Role.NOBODY) private Long id; @NotNull @@ -25,23 +53,21 @@ public class CustomerDTO implements Serializable { @NotNull @Size(max = 400) - // visible by >=contractual contact - // changeable by >=supporter + @ReadableFor(Role.CONTRACTUAL_CONTACT) private String contractualAddress; @Size(max = 80) - // visible by >=contractual contact - // changeable by >=supporter + @ReadableFor(Role.CONTRACTUAL_CONTACT) + @WritableFor(Role.CONTRACTUAL_CONTACT) private String contractualSalutation; @Size(max = 400) - // visible by >=contractual contact | >=billing contact - // changeable by >=contractual contact + @ReadableFor(Role.CONTRACTUAL_CONTACT) private String billingAddress; @Size(max = 80) - // visible by >=contractual contact | >=billing contact - // changeable by >=contractual contact + @ReadableFor(Role.CONTRACTUAL_CONTACT) + @WritableFor(Role.CONTRACTUAL_CONTACT) private String billingSalutation; public Long getId() { @@ -142,4 +168,133 @@ public class CustomerDTO implements Serializable { ", billingSalutation='" + getBillingSalutation() + "'" + "}"; } + + @JsonComponent + public static class CustomerJsonSerializer extends JsonSerializer { + + private Optional login; + + @PostConstruct + public void getLoginUser() { + this.login = SecurityUtils.getCurrentUserLogin(); + } + + @Override + public void serialize(CustomerDTO dto, JsonGenerator jsonGenerator, + SerializerProvider serializerProvider) throws IOException { + + jsonGenerator.writeStartObject(); + try { + for (PropertyDescriptor prop : Introspector.getBeanInfo(CustomerDTO.class).getPropertyDescriptors()) { + if (isRealProprety(prop)) { + toJSon(dto, jsonGenerator, prop); + } + } + } catch (IntrospectionException e) { + throw new RuntimeException(e); + } + +// +// jsonGenerator.writeNumberField("number", dto.getNumber()); +// jsonGenerator.writeStringField("prefix", dto.getPrefix()); +// jsonGenerator.writeStringField("name", dto.getName()); +// toJSonString(dto, jsonGenerator,"contractualAddress"); +// jsonGenerator.writeStringField("contractualSalutation", dto.getContractualSalutation()); +// jsonGenerator.writeStringField("billingAddress", dto.getBillingAddress()); +// jsonGenerator.writeStringField("billingSalutation", dto.getBillingSalutation()); + + jsonGenerator.writeEndObject(); + } + + private boolean isRealProprety(PropertyDescriptor prop) { + return prop.getWriteMethod() != null; + } + + private void toJSonString(CustomerDTO user, JsonGenerator jsonGenerator, String fieldName) throws IOException { + if (isReadAllowed(fieldName)) { + jsonGenerator.writeStringField(fieldName, user.getContractualAddress()); + } + } + + private void toJSon(CustomerDTO dto, JsonGenerator jsonGenerator, PropertyDescriptor prop) throws IOException { + final String fieldName = prop.getName(); + if (isReadAllowed(fieldName)) { + if (Integer.class.isAssignableFrom(prop.getPropertyType()) || int.class.isAssignableFrom(prop.getPropertyType())) { + jsonGenerator.writeNumberField(fieldName, (int) invoke(dto, prop.getReadMethod())); + } else if (Long.class.isAssignableFrom(prop.getPropertyType()) || long.class.isAssignableFrom(prop.getPropertyType())) { + jsonGenerator.writeNumberField(fieldName, (long) invoke(dto, prop.getReadMethod())); + } else if (String.class.isAssignableFrom(prop.getPropertyType())) { + jsonGenerator.writeStringField(fieldName, (String) invoke(dto, prop.getReadMethod())); + } else { + throw new NotImplementedException("property type not yet implemented" + prop); + } + } + } + + private Object invoke(Object dto, Method method) { + try { + return method.invoke(dto); + } catch (IllegalAccessException|InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + private boolean isReadAllowed(String fieldName) { + if ( fieldName.equals("contractualAddress") ) { + return login.map(user -> user.equals("admin")).orElse(false); + } + return true; + } + } + + @JsonComponent + public static class UserJsonDeserializer extends JsonDeserializer { + + @Override + public CustomerDTO deserialize(JsonParser jsonParser, + DeserializationContext deserializationContext) throws IOException, + JsonProcessingException { + + TreeNode treeNode = jsonParser.getCodec().readTree(jsonParser); + + CustomerDTO dto = new CustomerDTO(); + dto.setId(((IntNode) treeNode.get("id")).asLong()); + dto.setNumber(((IntNode) treeNode.get("number")).asInt()); + dto.setPrefix(((TextNode) treeNode.get("prefix")).asText()); + dto.setName(((TextNode) treeNode.get("name")).asText()); + dto.setContractualAddress(((TextNode) treeNode.get("contractualAddress")).asText()); + dto.setContractualSalutation(((TextNode) treeNode.get("contractualSalutation")).asText()); + dto.setBillingAddress(((TextNode) treeNode.get("billingAddress")).asText()); + dto.setBillingSalutation(((TextNode) treeNode.get("billingSalutation")).asText()); + + return dto; + } + } +} + +enum Role { + NOBODY(0), HOSTMASTER(1), ADMIN(2), SUPPORTER(3), + ANY_CUSTOMER_CONTACT(10), CONTRACTUAL_CONTACT(11), + ANY_CUSTOMER_USER(30); + + private final int level; + + Role(final int level) { + this.level = level; + } +} + +@Target({ElementType.FIELD, ElementType.TYPE_USE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@interface ReadableFor { + +} + + +@Target({ElementType.FIELD, ElementType.TYPE_USE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@interface WritableFor { + } diff --git a/src/test/java/org/hostsharing/hsadminng/service/dto/CustomerDTOUnitTest.java b/src/test/java/org/hostsharing/hsadminng/service/dto/CustomerDTOUnitTest.java new file mode 100644 index 00000000..472a235e --- /dev/null +++ b/src/test/java/org/hostsharing/hsadminng/service/dto/CustomerDTOUnitTest.java @@ -0,0 +1,113 @@ +package org.hostsharing.hsadminng.service.dto; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.hostsharing.hsadminng.security.SecurityUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.JsonTest; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.test.context.junit4.SpringRunner; + +import java.io.IOException; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; + +@JsonTest +@RunWith(SpringRunner.class) +public class CustomerDTOUnitTest { + + + @Autowired + private ObjectMapper objectMapper; + + @Test + public void testSerializationAsCustomer() throws JsonProcessingException { + + // given + CustomerDTO given = createSomeCustomerDTO(); + givenLoginUser("customer"); + + // when + String actual = objectMapper.writeValueAsString(given); + + // then + given.setContractualAddress(null); + //given.setContractualSalutation(null); + assertEquals(createExpectedJSon(given), actual); + } + + @Test + public void testDeserializeAsCustomer() throws IOException { + // given + String json = "{\"id\":1234,\"number\":10001,\"prefix\":\"abc\",\"name\":\"Mein Name\",\"contractualAddress\":\"Eine Adresse\",\"contractualSalutation\":\"Hallo\",\"billingAddress\":\"Noch eine Adresse\",\"billingSalutation\":\"Moin\"}"; + givenLoginUser("customer"); + + // when + CustomerDTO actual = objectMapper.readValue(json, CustomerDTO.class); + + // then + CustomerDTO expected = new CustomerDTO(); + expected.setId(1234L); + expected.setNumber(10001); + expected.setPrefix("abc"); + expected.setName("Mein Name"); + expected.setContractualAddress(null); // not allowed + expected.setContractualSalutation("Hallo"); + expected.setBillingAddress("Noch eine Adresse"); + expected.setBillingSalutation("Moin"); + assertEquals(actual, expected); + } + + private String createExpectedJSon(CustomerDTO dto) { + String json = // the fields in alphanumeric order: + toJSonFieldDefinitionIfPresent("billingAddress", dto.getBillingAddress()) + + toJSonFieldDefinitionIfPresent("billingSalutation", dto.getBillingSalutation()) + + toJSonFieldDefinitionIfPresent("contractualAddress", dto.getContractualAddress()) + + toJSonFieldDefinitionIfPresent("contractualSalutation", dto.getContractualSalutation()) + + toJSonFieldDefinitionIfPresent("id", dto.getId()) + + toJSonFieldDefinitionIfPresent("name", dto.getName()) + + toJSonFieldDefinitionIfPresent("number", dto.getNumber()) + + toJSonFieldDefinitionIfPresent("prefix", dto.getPrefix()); + return "{" + json.substring(0, json.length() - 1) + "}"; + } + + private String toJSonFieldDefinitionIfPresent(String name, String value) { + return value != null ? inQuotes(name) + ":" + inQuotes(value) + "," : ""; + } + + private String toJSonFieldDefinitionIfPresent(String name, Number value) { + return value != null ? inQuotes(name) + ":" + value + "," : ""; + } + + private String inQuotes(Object value) { + return "\"" + value.toString() + "\""; + } + + private CustomerDTO createSomeCustomerDTO() { + CustomerDTO given = new CustomerDTO(); + given.setId(1234L); + given.setNumber(10001); + given.setPrefix("abc"); + given.setName("Mein Name"); + given.setContractualAddress("Eine Adresse"); + given.setContractualSalutation("Hallo"); + given.setBillingAddress("Noch eine Adresse"); + given.setBillingSalutation("Moin"); + givenLoginUser("admin"); + return given; + } + + private void givenLoginUser(String userName) { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(new UsernamePasswordAuthenticationToken(userName, userName)); + SecurityContextHolder.setContext(securityContext); + Optional login = SecurityUtils.getCurrentUserLogin(); + assertThat(login).describedAs("precondition failed").contains(userName); + } +}