From 5d1136ee121fa099ca0ae8fe08572a2b5f10b165 Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Thu, 7 May 2020 22:19:28 +0200 Subject: [PATCH] #968 - Polishing. Original pull request: #1269. --- .../hateoas/mediatype/PropertyUtils.java | 73 ++++++++++--------- .../mediatype/hal/forms/HalFormsDocument.java | 2 +- .../Jackson2HalFormsIntegrationTest.java | 19 +++-- 3 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/main/java/org/springframework/hateoas/mediatype/PropertyUtils.java b/src/main/java/org/springframework/hateoas/mediatype/PropertyUtils.java index 96d1a7c19..73f234896 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/PropertyUtils.java +++ b/src/main/java/org/springframework/hateoas/mediatype/PropertyUtils.java @@ -15,7 +15,6 @@ */ package org.springframework.hateoas.mediatype; -import com.fasterxml.jackson.annotation.JsonUnwrapped; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; @@ -56,6 +55,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty.Access; +import com.fasterxml.jackson.annotation.JsonUnwrapped; /** * @author Greg Turnquist @@ -86,27 +86,7 @@ static List> getTypesToUnwrap() { } public static Map extractPropertyValues(@Nullable Object object) { - return extractPropertyValues(object, false); - } - - public static Map extractPropertyValues(@Nullable Object object, boolean unwrapEligibleProperties) { - - if (object == null) { - return Collections.emptyMap(); - } - - if (EntityModel.class.isInstance(object)) { - return extractPropertyValues(EntityModel.class.cast(object).getContent()); - } - - BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(object); - - return getExposedProperties(object.getClass()).stream() // - .map(PropertyMetadata::getName) - .map(name -> unwrapEligibleProperties ? unwrapPropertyIfNeeded(name, wrapper) : - Collections.singletonMap(name, wrapper.getPropertyValue(name))) - .flatMap(it -> it.entrySet().stream()) - .collect(HashMap::new, (map, it) -> map.put(it.getKey(), it.getValue()), HashMap::putAll); + return extractPropertyValues(object, true); } public static T createObjectFromProperties(Class clazz, Map properties) { @@ -116,13 +96,14 @@ public static T createObjectFromProperties(Class clazz, Map { Optional.ofNullable(BeanUtils.getPropertyDescriptor(clazz, key)) // .ifPresent(property -> { + try { Method writeMethod = property.getWriteMethod(); ReflectionUtils.makeAccessible(writeMethod); writeMethod.invoke(obj, value); - } catch (IllegalAccessException | InvocationTargetException e) { + } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } }); @@ -162,16 +143,13 @@ private static Map unwrapPropertyIfNeeded(String propertyName, B Field descriptorField = ReflectionUtils.findField(wrapper.getWrappedClass(), propertyName); Method readMethod = wrapper.getPropertyDescriptor(propertyName).getReadMethod(); - MergedAnnotation unwrappedAnnotation = - Stream.of(descriptorField, readMethod) - .filter(Objects::nonNull) - .map(MergedAnnotations::from) - .flatMap(mergedAnnotations -> mergedAnnotations.stream(JsonUnwrapped.class)) - .filter(it -> it.getBoolean("enabled")) - .findFirst() - .orElse(null); + MergedAnnotation unwrappedAnnotation = Stream.of(descriptorField, readMethod) + .filter(Objects::nonNull).map(MergedAnnotations::from) + .flatMap(mergedAnnotations -> mergedAnnotations.stream(JsonUnwrapped.class)) + .filter(it -> it.getBoolean("enabled")).findFirst().orElse(null); Object propertyValue = wrapper.getPropertyValue(propertyName); + if (unwrappedAnnotation == null) { return Collections.singletonMap(propertyName, propertyValue); } @@ -180,11 +158,34 @@ private static Map unwrapPropertyIfNeeded(String propertyName, B String suffix = unwrappedAnnotation.getString("suffix"); Map properties = new HashMap<>(); - extractPropertyValues(propertyValue, true) + + extractPropertyValues(propertyValue, true) // .forEach((name, value) -> properties.put(prefix + name + suffix, value)); + return properties; } + private static Map extractPropertyValues(@Nullable Object object, boolean unwrapEligibleProperties) { + + if (object == null) { + return Collections.emptyMap(); + } + + if (EntityModel.class.isInstance(object)) { + return extractPropertyValues(EntityModel.class.cast(object).getContent()); + } + + BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(object); + + return getExposedProperties(object.getClass()).stream() // + .map(PropertyMetadata::getName) // + .map(name -> unwrapEligibleProperties // + ? unwrapPropertyIfNeeded(name, wrapper) // + : Collections.singletonMap(name, wrapper.getPropertyValue(name))) // + .flatMap(it -> it.entrySet().stream()) // + .collect(HashMap::new, (map, it) -> map.put(it.getKey(), it.getValue()), HashMap::putAll); + } + private static ResolvableType unwrapDomainType(ResolvableType type) { if (!type.hasGenerics()) { @@ -203,7 +204,7 @@ private static ResolvableType unwrapDomainType(ResolvableType type) { * Replaces the given {@link ResolvableType} with the one produced by the given {@link Supplier} if the former is * assignable from one of the types to be unwrapped. * - * @param type must not be {@literal null}. + * @param type must not be {@literal null}. * @param mapper must not be {@literal null}. * @return * @see #TYPES_TO_UNWRAP @@ -222,8 +223,8 @@ private static Stream lookupExposedProperties(@Nullable Class< return type == null // ? Stream.empty() // : getPropertyDescriptors(type) // - .map(it -> new AnnotatedProperty(new Property(type, it.getReadMethod(), it.getWriteMethod()))) - .map(it -> JSR_303_PRESENT ? new Jsr303AwarePropertyMetadata(it) : new DefaultPropertyMetadata(it)); + .map(it -> new AnnotatedProperty(new Property(type, it.getReadMethod(), it.getWriteMethod()))) + .map(it -> JSR_303_PRESENT ? new Jsr303AwarePropertyMetadata(it) : new DefaultPropertyMetadata(it)); } /** @@ -393,7 +394,7 @@ public boolean hasWriteMethod() { /** * Returns the {@link MergedAnnotation} of the given type. * - * @param the annotation type. + * @param the annotation type. * @param type must not be {@literal null}. * @return the {@link MergedAnnotation} if available or {@link MergedAnnotation#missing()} if not. */ diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsDocument.java b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsDocument.java index e71b0db26..f033c234d 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsDocument.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsDocument.java @@ -108,7 +108,7 @@ private HalFormsDocument() { */ public static HalFormsDocument forRepresentationModel(RepresentationModel model) { - Map attributes = PropertyUtils.extractPropertyValues(model, true); + Map attributes = PropertyUtils.extractPropertyValues(model); attributes.remove("links"); return new HalFormsDocument<>().withAttributes(attributes); diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsIntegrationTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsIntegrationTest.java index 83a46b46d..bdf5c71bc 100644 --- a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsIntegrationTest.java @@ -17,7 +17,6 @@ import static org.assertj.core.api.Assertions.*; -import com.fasterxml.jackson.annotation.JsonUnwrapped; import lombok.Getter; import net.minidev.json.JSONArray; @@ -68,6 +67,7 @@ import org.springframework.lang.Nullable; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonUnwrapped; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.jayway.jsonpath.JsonPath; @@ -517,15 +517,14 @@ void considersJsr303AnnotationsForTemplates() throws Exception { assertValueForPath(model, "$._templates.default.properties[0].required", true); } - /** - * @see #968 - */ - @Test + @Test // #968 void considerJsonUnwrapped() throws Exception { + UnwrappedExample unwrappedExample = new UnwrappedExample(); + unwrappedExample.element = new UnwrappedExampleElement(); unwrappedExample.element.firstname = "john"; - + assertValueForPath(unwrappedExample, "$.firstname", "john"); } @@ -634,17 +633,17 @@ public String getFirstname() { return firstname; } } - + public static class UnwrappedExample extends RepresentationModel { - + private UnwrappedExampleElement element; @JsonUnwrapped - public UnwrappedExampleElement getElement(){ + public UnwrappedExampleElement getElement() { return element; } } - + public static class UnwrappedExampleElement { private @Getter String firstname; }