#729 - Fix serialization of RepresentationModel for HAL-FORMS.

Original pull request: #824.
This commit is contained in:
Greg Turnquist
2019-02-19 12:34:46 -06:00
committed by Oliver Drotbohm
parent 8f14f03b97
commit 0a5e8b98e5
7 changed files with 174 additions and 46 deletions

View File

@@ -41,20 +41,20 @@ import com.fasterxml.jackson.databind.type.TypeFactory;
*/
class HalFormsDeserializers {
static class HalFormsResourcesDeserializer extends ContainerDeserializerBase<List<Object>>
static class HalFormsCollectionModelDeserializer extends ContainerDeserializerBase<List<Object>>
implements ContextualDeserializer {
private static final long serialVersionUID = -7325599536381465624L;
private JavaType contentType;
HalFormsResourcesDeserializer(JavaType contentType) {
HalFormsCollectionModelDeserializer(JavaType contentType) {
super(contentType);
this.contentType = contentType;
}
HalFormsResourcesDeserializer() {
HalFormsCollectionModelDeserializer() {
this(TypeFactory.defaultInstance().constructCollectionLikeType(List.class, Object.class));
}
@@ -119,7 +119,7 @@ class HalFormsDeserializers {
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
throws JsonMappingException {
return new HalFormsResourcesDeserializer(
return new HalFormsCollectionModelDeserializer(
property == null ? ctxt.getContextualType() : property.getType().getContentType());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 the original author or authors.
* Copyright 2017-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.springframework.hateoas.mediatype.hal.forms;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.experimental.Wither;
@@ -29,12 +30,15 @@ import org.springframework.hateoas.Link;
import org.springframework.hateoas.Links;
import org.springframework.hateoas.PagedModel;
import org.springframework.hateoas.PagedModel.PageMetadata;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.hateoas.mediatype.PropertyUtils;
import org.springframework.hateoas.mediatype.hal.HalLinkRelation;
import org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer;
import org.springframework.hateoas.mediatype.hal.forms.Jackson2HalFormsModule.HalFormsLinksDeserializer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
@@ -54,10 +58,15 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@Value
@Wither
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
@JsonPropertyOrder({ "resource", "resources", "embedded", "links", "templates", "metadata" })
@JsonPropertyOrder({ "attributes", "resource", "resources", "embedded", "links", "templates", "metadata" })
public class HalFormsDocument<T> {
@Nullable //
@Getter(onMethod = @__(@JsonAnyGetter))
@JsonInclude(Include.NON_EMPTY) //
@Wither(AccessLevel.PRIVATE) //
private Map<String, Object> attributes;
@JsonUnwrapped //
@JsonInclude(Include.NON_NULL) //
private T resource;
@@ -90,7 +99,21 @@ public class HalFormsDocument<T> {
private Map<String, HalFormsTemplate> templates;
private HalFormsDocument() {
this(null, null, Collections.emptyMap(), null, Links.NONE, Collections.emptyMap());
this(null, null, null, Collections.emptyMap(), null, Links.NONE, Collections.emptyMap());
}
/**
* Creates a new {@link HalFormsDocument} for the given resource support.
*
* @param model can be {@literal null}
* @return
*/
public static HalFormsDocument<?> forRepresentationModel(RepresentationModel<?> model) {
Map<String, Object> attributes = PropertyUtils.findProperties(model);
attributes.remove("links");
return new HalFormsDocument<>().withAttributes(attributes);
}
/**
@@ -150,11 +173,11 @@ public class HalFormsDocument<T> {
}
public HalFormsDocument<T> withPageMetadata(@Nullable PageMetadata metadata) {
return new HalFormsDocument<T>(resource, resources, embedded, metadata, links, templates);
return new HalFormsDocument<T>(attributes, resource, resources, embedded, metadata, links, templates);
}
private HalFormsDocument<T> withResource(@Nullable T resource) {
return new HalFormsDocument<T>(resource, resources, embedded, pageMetadata, links, templates);
return new HalFormsDocument<T>(attributes, resource, resources, embedded, pageMetadata, links, templates);
}
/**
@@ -167,7 +190,7 @@ public class HalFormsDocument<T> {
Assert.notNull(link, "Link must not be null!");
return new HalFormsDocument<>(resource, resources, embedded, pageMetadata, links.and(link), templates);
return new HalFormsDocument<>(attributes, resource, resources, embedded, pageMetadata, links.and(link), templates);
}
/**
@@ -185,7 +208,7 @@ public class HalFormsDocument<T> {
Map<String, HalFormsTemplate> templates = new HashMap<>(this.templates);
templates.put(name, template);
return new HalFormsDocument<>(resource, resources, embedded, pageMetadata, links, templates);
return new HalFormsDocument<>(attributes, resource, resources, embedded, pageMetadata, links, templates);
}
/**
@@ -203,10 +226,6 @@ public class HalFormsDocument<T> {
Map<HalLinkRelation, Object> embedded = new HashMap<>(this.embedded);
embedded.put(key, value);
return new HalFormsDocument<>(resource, resources, embedded, pageMetadata, links, templates);
}
HalFormsDocument<T> withLinks(Links links) {
return new HalFormsDocument<>(resource, resources, embedded, pageMetadata, links, templates);
return new HalFormsDocument<>(attributes, resource, resources, embedded, pageMetadata, links, templates);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 the original author or authors.
* Copyright 2017-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -59,24 +59,94 @@ import com.fasterxml.jackson.databind.ser.ContextualSerializer;
*/
class HalFormsSerializers {
static class HalFormsRepresentationModelSerializer extends ContainerSerializer<RepresentationModel<?>>
implements ContextualSerializer {
private static final long serialVersionUID = -4583146321934407153L;
private final MessageSourceAccessor accessor;
private final BeanProperty property;
HalFormsRepresentationModelSerializer(MessageSourceAccessor accessor, @Nullable BeanProperty property) {
super(RepresentationModel.class, false);
this.property = property;
this.accessor = accessor;
}
HalFormsRepresentationModelSerializer(MessageSourceAccessor accessor) {
this(accessor, null);
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.std.StdSerializer#serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider)
*/
@Override
@SuppressWarnings("null")
public void serialize(RepresentationModel<?> value, JsonGenerator gen, SerializerProvider provider)
throws IOException {
HalFormsDocument<?> doc = HalFormsDocument.forRepresentationModel(value) //
.withLinks(value.getLinks()) //
.withTemplates(findTemplates(value, accessor));
provider.findValueSerializer(HalFormsDocument.class, property).serialize(doc, gen, provider);
}
@Override
@Nullable
@SuppressWarnings("null")
public JavaType getContentType() {
return null;
}
@Override
@Nullable
@SuppressWarnings("null")
public JsonSerializer<?> getContentSerializer() {
return null;
}
@Override
@SuppressWarnings("null")
public boolean hasSingleElement(RepresentationModel<?> resource) {
return false;
}
@Override
@Nullable
@SuppressWarnings("null")
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer typeSerializer) {
return null;
}
@Override
@SuppressWarnings("null")
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
return new HalFormsRepresentationModelSerializer(accessor, property);
}
}
/**
* Serializer for {@link CollectionModel}.
*/
static class HalFormsResourceSerializer extends ContainerSerializer<EntityModel<?>> implements ContextualSerializer {
static class HalFormsEntityModelSerializer extends ContainerSerializer<EntityModel<?>>
implements ContextualSerializer {
private static final long serialVersionUID = -7912243216469101379L;
private final MessageSourceAccessor accessor;
private final BeanProperty property;
HalFormsResourceSerializer(MessageSourceAccessor accessor, @Nullable BeanProperty property) {
HalFormsEntityModelSerializer(MessageSourceAccessor accessor, @Nullable BeanProperty property) {
super(EntityModel.class, false);
this.accessor = accessor;
this.property = property;
}
HalFormsResourceSerializer(MessageSourceAccessor accessor) {
HalFormsEntityModelSerializer(MessageSourceAccessor accessor) {
this(accessor, null);
}
@@ -144,14 +214,14 @@ class HalFormsSerializers {
@SuppressWarnings("null")
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException {
return new HalFormsResourceSerializer(accessor, property);
return new HalFormsEntityModelSerializer(accessor, property);
}
}
/**
* Serializer for {@link CollectionModel}
*/
static class HalFormsResourcesSerializer extends ContainerSerializer<CollectionModel<?>>
static class HalFormsCollectionModelSerializer extends ContainerSerializer<CollectionModel<?>>
implements ContextualSerializer {
private static final long serialVersionUID = -3601146866067500734L;
@@ -160,7 +230,7 @@ class HalFormsSerializers {
private final Jackson2HalModule.EmbeddedMapper embeddedMapper;
private final MessageSourceAccessor accessor;
HalFormsResourcesSerializer(MessageSourceAccessor accessor, @Nullable BeanProperty property,
HalFormsCollectionModelSerializer(MessageSourceAccessor accessor, @Nullable BeanProperty property,
Jackson2HalModule.EmbeddedMapper embeddedMapper) {
super(CollectionModel.class, false);
@@ -170,7 +240,7 @@ class HalFormsSerializers {
this.accessor = accessor;
}
HalFormsResourcesSerializer(MessageSourceAccessor accessor, Jackson2HalModule.EmbeddedMapper embeddedMapper) {
HalFormsCollectionModelSerializer(MessageSourceAccessor accessor, Jackson2HalModule.EmbeddedMapper embeddedMapper) {
this(accessor, null, embeddedMapper);
}
@@ -254,7 +324,7 @@ class HalFormsSerializers {
@SuppressWarnings("null")
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException {
return new HalFormsResourcesSerializer(accessor, property, embeddedMapper);
return new HalFormsCollectionModelSerializer(accessor, property, embeddedMapper);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2018 the original author or authors.
* Copyright 2016-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,10 +34,10 @@ import org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalHandlerIns
import org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListDeserializer;
import org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer;
import org.springframework.hateoas.mediatype.hal.LinkMixin;
import org.springframework.hateoas.mediatype.hal.RepresentationModelMixin;
import org.springframework.hateoas.mediatype.hal.forms.HalFormsDeserializers.HalFormsResourcesDeserializer;
import org.springframework.hateoas.mediatype.hal.forms.HalFormsSerializers.HalFormsResourceSerializer;
import org.springframework.hateoas.mediatype.hal.forms.HalFormsSerializers.HalFormsResourcesSerializer;
import org.springframework.hateoas.mediatype.hal.forms.HalFormsDeserializers.HalFormsCollectionModelDeserializer;
import org.springframework.hateoas.mediatype.hal.forms.HalFormsSerializers.HalFormsCollectionModelSerializer;
import org.springframework.hateoas.mediatype.hal.forms.HalFormsSerializers.HalFormsEntityModelSerializer;
import org.springframework.hateoas.mediatype.hal.forms.HalFormsSerializers.HalFormsRepresentationModelSerializer;
import org.springframework.hateoas.server.LinkRelationProvider;
import org.springframework.hateoas.server.mvc.JacksonSerializers.MediaTypeDeserializer;
import org.springframework.http.MediaType;
@@ -93,16 +93,19 @@ class Jackson2HalFormsModule extends SimpleModule {
@JsonSerialize(using = HalLinkListSerializer.class)
abstract class LinksMixin {}
@JsonSerialize(using = HalFormsResourceSerializer.class)
@JsonSerialize(using = HalFormsRepresentationModelSerializer.class)
abstract class RepresentationModelMixin extends org.springframework.hateoas.mediatype.hal.RepresentationModelMixin {}
@JsonSerialize(using = HalFormsEntityModelSerializer.class)
abstract class EntityModelMixin<T> extends EntityModel<T> {}
@JsonSerialize(using = HalFormsResourcesSerializer.class)
@JsonSerialize(using = HalFormsCollectionModelSerializer.class)
abstract class CollectionModelMixin<T> extends CollectionModel<T> {
@Override
@JsonProperty("_embedded")
@JsonInclude(Include.NON_EMPTY)
@JsonDeserialize(using = HalFormsResourcesDeserializer.class)
@JsonDeserialize(using = HalFormsCollectionModelDeserializer.class)
public abstract Collection<T> getContent();
}
@@ -166,8 +169,11 @@ class Jackson2HalFormsModule extends SimpleModule {
EmbeddedMapper mapper = new EmbeddedMapper(resolver, curieProvider, enforceEmbeddedCollections);
this.serializers.put(HalFormsResourceSerializer.class, new HalFormsResourceSerializer(accessor));
this.serializers.put(HalFormsResourcesSerializer.class, new HalFormsResourcesSerializer(accessor, mapper));
this.serializers.put(HalFormsRepresentationModelSerializer.class,
new HalFormsRepresentationModelSerializer(accessor));
this.serializers.put(HalFormsEntityModelSerializer.class, new HalFormsEntityModelSerializer(accessor));
this.serializers.put(HalFormsCollectionModelSerializer.class,
new HalFormsCollectionModelSerializer(accessor, mapper));
this.serializers.put(HalLinkListSerializer.class,
new HalLinkListSerializer(curieProvider, mapper, accessor, configuration.getHalConfiguration()));
}