#1447 - Overhaul Jackson customizations for HAL FORMS.
Got rid of quite a few custom serializers in Jackson2HalFormsModule. We now rely on the HAL setup mostly and register a virtual bean property to render the _templates field. The Jackson component used for that is registered as Spring bean so that we can now use the HAL HandlerInstantiator with the Jackson2HalFormsModule. Moved quite a few integration tests to the COntextualMapper API.
This commit is contained in:
@@ -10,7 +10,6 @@
|
||||
"_templates" : {
|
||||
"default" : {
|
||||
"method" : "put",
|
||||
"contentType" : "",
|
||||
"properties" : [ {
|
||||
"name" : "firstName",
|
||||
"required" : true
|
||||
@@ -24,7 +23,6 @@
|
||||
},
|
||||
"partiallyUpdateEmployee" : {
|
||||
"method" : "patch",
|
||||
"contentType" : "",
|
||||
"properties" : [ {
|
||||
"name" : "firstName",
|
||||
"required" : false
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.hateoas.mediatype;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationConfig;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.KeyDeserializer;
|
||||
import com.fasterxml.jackson.databind.SerializationConfig;
|
||||
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
|
||||
import com.fasterxml.jackson.databind.cfg.MapperConfig;
|
||||
import com.fasterxml.jackson.databind.introspect.Annotated;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
|
||||
import com.fasterxml.jackson.databind.ser.VirtualBeanPropertyWriter;
|
||||
|
||||
/**
|
||||
* A {@link HandlerInstantiator} that will use instances explicitly registered with it but fall back to lookup or even
|
||||
* create a fresh instance via the {@link AutowireCapableBeanFactory} provided on construction.
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@SuppressWarnings("null")
|
||||
public class ConfigurableHandlerInstantiator extends HandlerInstantiator {
|
||||
|
||||
private final Map<Class<?>, Object> instances = new HashMap<>();
|
||||
private final AutowireCapableBeanFactory beanFactory;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ConfigurableHandlerInstantiator} for the given {@link AutowireCapableBeanFactory}.
|
||||
*
|
||||
* @param beanFactory must not be {@literal null}.
|
||||
*/
|
||||
protected ConfigurableHandlerInstantiator(AutowireCapableBeanFactory beanFactory) {
|
||||
|
||||
Assert.notNull(beanFactory, "BeanFactory must not be null!");
|
||||
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
protected void registerInstance(Object instance) {
|
||||
this.instances.put(instance.getClass(), instance);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> T findInstance(Class<T> type) {
|
||||
return (T) this.instances.get(type);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> T findOrCreateInstance(Class<T> type) {
|
||||
|
||||
Object object = findInstance(type);
|
||||
|
||||
return object != null
|
||||
? (T) object
|
||||
: beanFactory.getBeanProvider(type)
|
||||
.getIfAvailable(() -> beanFactory.createBean(type));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#deserializerInstance(com.fasterxml.jackson.databind.DeserializationConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public JsonDeserializer<?> deserializerInstance(DeserializationConfig config, Annotated annotated,
|
||||
Class<?> deserClass) {
|
||||
return (JsonDeserializer<?>) findOrCreateInstance(deserClass);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#keyDeserializerInstance(com.fasterxml.jackson.databind.DeserializationConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public KeyDeserializer keyDeserializerInstance(DeserializationConfig config, Annotated annotated,
|
||||
Class<?> keyDeserClass) {
|
||||
return (KeyDeserializer) findOrCreateInstance(keyDeserClass);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#serializerInstance(com.fasterxml.jackson.databind.SerializationConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public JsonSerializer<?> serializerInstance(SerializationConfig config, Annotated annotated, Class<?> serClass) {
|
||||
return (JsonSerializer<?>) findOrCreateInstance(serClass);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#typeResolverBuilderInstance(com.fasterxml.jackson.databind.cfg.MapperConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public TypeResolverBuilder<?> typeResolverBuilderInstance(MapperConfig<?> config, Annotated annotated,
|
||||
Class<?> builderClass) {
|
||||
return (TypeResolverBuilder<?>) findOrCreateInstance(builderClass);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#typeIdResolverInstance(com.fasterxml.jackson.databind.cfg.MapperConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public TypeIdResolver typeIdResolverInstance(MapperConfig<?> config, Annotated annotated, Class<?> resolverClass) {
|
||||
return (TypeIdResolver) findOrCreateInstance(resolverClass);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#virtualPropertyWriterInstance(com.fasterxml.jackson.databind.cfg.MapperConfig, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public VirtualBeanPropertyWriter virtualPropertyWriterInstance(MapperConfig<?> config, Class<?> implClass) {
|
||||
return (VirtualBeanPropertyWriter) findOrCreateInstance(implClass);
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@JsonPropertyOrder({ "content", "links" })
|
||||
abstract class CollectionModelMixin<T> extends CollectionModel<T> {
|
||||
public abstract class CollectionModelMixin<T> extends CollectionModel<T> {
|
||||
|
||||
@Override
|
||||
@JsonProperty("_embedded")
|
||||
|
||||
@@ -26,23 +26,24 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.hateoas.CollectionModel;
|
||||
import org.springframework.hateoas.EntityModel;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.LinkRelation;
|
||||
import org.springframework.hateoas.Links;
|
||||
import org.springframework.hateoas.RepresentationModel;
|
||||
import org.springframework.hateoas.mediatype.ConfigurableHandlerInstantiator;
|
||||
import org.springframework.hateoas.mediatype.MessageResolver;
|
||||
import org.springframework.hateoas.mediatype.hal.HalConfiguration.RenderSingleLinks;
|
||||
import org.springframework.hateoas.server.LinkRelationProvider;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParseException;
|
||||
@@ -51,14 +52,9 @@ import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.core.Version;
|
||||
import com.fasterxml.jackson.databind.*;
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategy.PropertyNamingStrategyBase;
|
||||
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
|
||||
import com.fasterxml.jackson.databind.cfg.MapperConfig;
|
||||
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
|
||||
import com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase;
|
||||
import com.fasterxml.jackson.databind.introspect.Annotated;
|
||||
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.databind.ser.ContainerSerializer;
|
||||
@@ -711,32 +707,32 @@ public class Jackson2HalModule extends SimpleModule {
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public static class HalHandlerInstantiator extends HandlerInstantiator {
|
||||
|
||||
private final Map<Class<?>, Object> serializers = new HashMap<>();
|
||||
private final @Nullable AutowireCapableBeanFactory delegate;
|
||||
public static class HalHandlerInstantiator extends ConfigurableHandlerInstantiator {
|
||||
|
||||
/**
|
||||
* Convenience constructor for testing purposes. Prefer
|
||||
* {@link #HalHandlerInstantiator(LinkRelationProvider, CurieProvider, MessageResolver, HalConfiguration, AutowireCapableBeanFactory)}
|
||||
*
|
||||
* @param provider must not be {@literal null}.
|
||||
* @param curieProvider must not be {@literal null}.
|
||||
* @param resolver must not be {@literal null}.
|
||||
*/
|
||||
public HalHandlerInstantiator(LinkRelationProvider provider, CurieProvider curieProvider,
|
||||
MessageResolver resolver) {
|
||||
this(provider, curieProvider, resolver, new HalConfiguration());
|
||||
this(provider, curieProvider, resolver, new HalConfiguration(), new DefaultListableBeanFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link HalHandlerInstantiator} using the given {@link LinkRelationProvider}, {@link CurieProvider}
|
||||
* and {@link MessageResolver}. Registers a prepared {@link HalResourcesSerializer} and
|
||||
* {@link HalLinkListSerializer} falling back to instantiation expecting a default constructor.
|
||||
*
|
||||
* @param provider must not be {@literal null}.
|
||||
* @param curieProvider can be {@literal null}.
|
||||
* @param curieProvider must not be {@literal null}.
|
||||
* @param resolver must not be {@literal null}.
|
||||
* @param halConfiguration must not be {@literal null}.
|
||||
* @param delegate must not be {@literal null}.
|
||||
*/
|
||||
public HalHandlerInstantiator(LinkRelationProvider provider, CurieProvider curieProvider, MessageResolver resolver,
|
||||
HalConfiguration halConfiguration) {
|
||||
this(provider, curieProvider, resolver, halConfiguration, null);
|
||||
}
|
||||
HalConfiguration halConfiguration, AutowireCapableBeanFactory delegate) {
|
||||
|
||||
public HalHandlerInstantiator(LinkRelationProvider provider, CurieProvider curieProvider, MessageResolver resolver,
|
||||
HalConfiguration halConfiguration, @Nullable AutowireCapableBeanFactory delegate) {
|
||||
super(delegate);
|
||||
|
||||
Assert.notNull(provider, "RelProvider must not be null!");
|
||||
Assert.notNull(curieProvider, "CurieProvider must not be null!");
|
||||
@@ -744,73 +740,8 @@ public class Jackson2HalModule extends SimpleModule {
|
||||
EmbeddedMapper mapper = new EmbeddedMapper(provider, curieProvider,
|
||||
halConfiguration.isEnforceEmbeddedCollections());
|
||||
|
||||
this.delegate = delegate;
|
||||
|
||||
this.serializers.put(HalResourcesSerializer.class, new HalResourcesSerializer(mapper, halConfiguration));
|
||||
this.serializers.put(HalLinkListSerializer.class,
|
||||
new HalLinkListSerializer(curieProvider, mapper, resolver, halConfiguration));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#deserializerInstance(com.fasterxml.jackson.databind.DeserializationConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("null")
|
||||
public JsonDeserializer<?> deserializerInstance(@NonNull DeserializationConfig config, @NonNull Annotated annotated,
|
||||
@NonNull Class<?> deserClass) {
|
||||
return (JsonDeserializer<?>) findInstance(deserClass);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#keyDeserializerInstance(com.fasterxml.jackson.databind.DeserializationConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("null")
|
||||
public KeyDeserializer keyDeserializerInstance(@NonNull DeserializationConfig config, @NonNull Annotated annotated,
|
||||
@NonNull Class<?> keyDeserClass) {
|
||||
return (KeyDeserializer) findInstance(keyDeserClass);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#serializerInstance(com.fasterxml.jackson.databind.SerializationConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("null")
|
||||
public JsonSerializer<?> serializerInstance(@NonNull SerializationConfig config, @NonNull Annotated annotated,
|
||||
@NonNull Class<?> serClass) {
|
||||
return (JsonSerializer<?>) findInstance(serClass);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#typeResolverBuilderInstance(com.fasterxml.jackson.databind.cfg.MapperConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("null")
|
||||
public TypeResolverBuilder<?> typeResolverBuilderInstance(@NonNull MapperConfig<?> config,
|
||||
@NonNull Annotated annotated, @NonNull Class<?> builderClass) {
|
||||
return (TypeResolverBuilder<?>) findInstance(builderClass);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.cfg.HandlerInstantiator#typeIdResolverInstance(com.fasterxml.jackson.databind.cfg.MapperConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("null")
|
||||
public TypeIdResolver typeIdResolverInstance(@NonNull MapperConfig<?> config, @NonNull Annotated annotated,
|
||||
@NonNull Class<?> resolverClass) {
|
||||
return (TypeIdResolver) findInstance(resolverClass);
|
||||
}
|
||||
|
||||
private Object findInstance(Class<?> type) {
|
||||
|
||||
Object result = serializers.get(type);
|
||||
|
||||
return result != null ? result : delegate != null ? delegate.createBean(type) : BeanUtils.instantiateClass(type);
|
||||
registerInstance(new HalResourcesSerializer(mapper, halConfiguration));
|
||||
registerInstance(new HalLinkListSerializer(curieProvider, mapper, resolver, halConfiguration));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -988,6 +919,7 @@ public class Jackson2HalModule extends SimpleModule {
|
||||
|
||||
@Nullable
|
||||
@JsonInclude(Include.NON_EMPTY)
|
||||
@JsonProperty
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
@@ -1,379 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017-2021 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.hateoas.mediatype.hal.forms;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
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;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
|
||||
/**
|
||||
* Representation of a HAL-FORMS document.
|
||||
*
|
||||
* @author Dietrich Schulten
|
||||
* @author Greg Turnquist
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@JsonPropertyOrder({ "attributes", "entity", "entities", "embedded", "links", "templates", "metadata" })
|
||||
final class HalFormsDocument<T> {
|
||||
|
||||
@Nullable //
|
||||
@JsonInclude(Include.NON_EMPTY) //
|
||||
private final Map<String, Object> attributes;
|
||||
|
||||
@Nullable //
|
||||
@JsonUnwrapped //
|
||||
@JsonInclude(Include.NON_NULL) //
|
||||
private final T entity;
|
||||
|
||||
@Nullable //
|
||||
@JsonInclude(Include.NON_EMPTY) //
|
||||
@JsonIgnore //
|
||||
private final Collection<T> entities;
|
||||
|
||||
@JsonProperty("_embedded") //
|
||||
@JsonInclude(Include.NON_EMPTY) //
|
||||
private final Map<HalLinkRelation, Object> embedded;
|
||||
|
||||
@Nullable //
|
||||
@JsonProperty("page") //
|
||||
@JsonInclude(Include.NON_NULL) //
|
||||
private final PagedModel.PageMetadata pageMetadata;
|
||||
|
||||
@JsonProperty("_links") //
|
||||
@JsonInclude(Include.NON_EMPTY) //
|
||||
@JsonSerialize(using = HalLinkListSerializer.class) //
|
||||
@JsonDeserialize(using = HalFormsLinksDeserializer.class) //
|
||||
private final Links links;
|
||||
|
||||
@JsonProperty("_templates") //
|
||||
@JsonInclude(Include.NON_EMPTY) //
|
||||
private final Map<String, HalFormsTemplate> templates;
|
||||
|
||||
HalFormsDocument(Map<String, Object> attributes, T entity, Collection<T> entities,
|
||||
Map<HalLinkRelation, Object> embedded, PageMetadata pageMetadata, Links links,
|
||||
Map<String, HalFormsTemplate> templates) {
|
||||
|
||||
this.attributes = attributes;
|
||||
this.entity = entity;
|
||||
this.entities = entities;
|
||||
this.embedded = embedded;
|
||||
this.pageMetadata = pageMetadata;
|
||||
this.links = links;
|
||||
this.templates = templates;
|
||||
}
|
||||
|
||||
private HalFormsDocument() {
|
||||
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
|
||||
*/
|
||||
static HalFormsDocument<?> forRepresentationModel(RepresentationModel<?> model) {
|
||||
|
||||
Map<String, Object> attributes = PropertyUtils.extractPropertyValues(model);
|
||||
attributes.remove("links");
|
||||
|
||||
return new HalFormsDocument<>().withAttributes(attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link HalFormsDocument} for the given resource.
|
||||
*
|
||||
* @param resource can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
static <T> HalFormsDocument<T> forEntity(@Nullable T resource) {
|
||||
return new HalFormsDocument<T>().withEntity(resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a new {@link HalFormsDocument} for the given resources.
|
||||
*
|
||||
* @param entities must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
static <T> HalFormsDocument<T> forEntities(Collection<T> entities) {
|
||||
|
||||
Assert.notNull(entities, "Resources must not be null!");
|
||||
|
||||
return new HalFormsDocument<T>().withEntities(entities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new empty {@link HalFormsDocument}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
static HalFormsDocument<?> empty() {
|
||||
return new HalFormsDocument<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link HalFormsDocument} by copying attributes and replacing the {@literal attributes}.
|
||||
*
|
||||
* @param attributes
|
||||
* @return
|
||||
*/
|
||||
private HalFormsDocument<T> withAttributes(@Nullable Map<String, Object> attributes) {
|
||||
|
||||
return this.attributes == attributes ? this
|
||||
: new HalFormsDocument<T>(attributes, this.entity, this.entities, this.embedded, this.pageMetadata, this.links,
|
||||
this.templates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link HalFormsDocument} by copying attributes and replacing {@literal entity}.
|
||||
*
|
||||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
private HalFormsDocument<T> withEntity(@Nullable T entity) {
|
||||
|
||||
return this.entity == entity ? this
|
||||
: new HalFormsDocument<T>(this.attributes, entity, this.entities, this.embedded, this.pageMetadata, this.links,
|
||||
this.templates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link HalFormsDocument} by copying attributes and replacing the {@literal entities}.
|
||||
*
|
||||
* @param entities
|
||||
* @return
|
||||
*/
|
||||
private HalFormsDocument<T> withEntities(@Nullable Collection<T> entities) {
|
||||
|
||||
return this.entities == entities ? this
|
||||
: new HalFormsDocument<T>(this.attributes, this.entity, entities, this.embedded, this.pageMetadata, this.links,
|
||||
this.templates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link HalFormsDocument} by copying the attributes and adding a new embedded value.
|
||||
*
|
||||
* @param key must not be {@literal null} or empty.
|
||||
* @param value must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
HalFormsDocument<T> andEmbedded(HalLinkRelation key, Object value) {
|
||||
|
||||
Assert.notNull(key, "Embedded key must not be null!");
|
||||
Assert.notNull(value, "Embedded value must not be null!");
|
||||
|
||||
Map<HalLinkRelation, Object> embedded = new HashMap<>(this.embedded);
|
||||
embedded.put(key, value);
|
||||
|
||||
return new HalFormsDocument<>(this.attributes, this.entity, this.entities, embedded, this.pageMetadata, this.links,
|
||||
this.templates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link HalFormsDocument} by copying attributes and replacing all {@literal embedded}s.
|
||||
*
|
||||
* @param embedded
|
||||
* @return
|
||||
*/
|
||||
HalFormsDocument<T> withEmbedded(Map<HalLinkRelation, Object> embedded) {
|
||||
|
||||
return this.embedded == embedded ? this
|
||||
: new HalFormsDocument<T>(this.attributes, this.entity, this.entities, embedded, this.pageMetadata, this.links,
|
||||
this.templates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link HalFormsDocument} by copying attributes and replacing the {@literal pageMetadata}.
|
||||
*
|
||||
* @param pageMetadata
|
||||
* @return
|
||||
*/
|
||||
HalFormsDocument<T> withPageMetadata(@Nullable PageMetadata pageMetadata) {
|
||||
|
||||
return this.pageMetadata == pageMetadata ? this
|
||||
: new HalFormsDocument<T>(this.attributes, this.entity, this.entities, this.embedded, pageMetadata, this.links,
|
||||
this.templates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link HalFormsDocument} by copying the attributes and adding a new {@link Link}.
|
||||
*
|
||||
* @param link must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
HalFormsDocument<T> andLink(Link link) {
|
||||
|
||||
Assert.notNull(link, "Link must not be null!");
|
||||
|
||||
return new HalFormsDocument<>(this.attributes, this.entity, this.entities, this.embedded, this.pageMetadata,
|
||||
this.links.and(link), this.templates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link HalFormsDocument} by copying attributes and replacing the {@literal links}.
|
||||
*
|
||||
* @param links
|
||||
* @return
|
||||
*/
|
||||
HalFormsDocument<T> withLinks(Links links) {
|
||||
|
||||
return this.links == links ? this
|
||||
: new HalFormsDocument<T>(this.attributes, this.entity, this.entities, this.embedded, this.pageMetadata, links,
|
||||
this.templates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link HalFormsDocument} by copying the attributes and adding a new {@link HalFormsTemplate}.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @param template must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
HalFormsDocument<T> andTemplate(String name, HalFormsTemplate template) {
|
||||
|
||||
Assert.hasText(name, "Template name must not be null or empty!");
|
||||
Assert.notNull(template, "Template must not be null!");
|
||||
|
||||
Map<String, HalFormsTemplate> templates = new HashMap<>(this.templates);
|
||||
templates.put(name, template);
|
||||
|
||||
return new HalFormsDocument<>(this.attributes, this.entity, this.entities, this.embedded, this.pageMetadata,
|
||||
this.links, templates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link HalFormsDocument} by copying attributes and replacing the {@literal templates}.
|
||||
*
|
||||
* @param templates
|
||||
* @return
|
||||
*/
|
||||
HalFormsDocument<T> withTemplates(Map<String, HalFormsTemplate> templates) {
|
||||
|
||||
return this.templates == templates ? this
|
||||
: new HalFormsDocument<T>(this.attributes, this.entity, this.entities, this.embedded, this.pageMetadata,
|
||||
this.links, templates);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@JsonAnyGetter
|
||||
Map<String, Object> getAttributes() {
|
||||
return this.attributes;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
T getEntity() {
|
||||
return this.entity;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Collection<T> getEntities() {
|
||||
return this.entities;
|
||||
}
|
||||
|
||||
Map<HalLinkRelation, Object> getEmbedded() {
|
||||
return this.embedded;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
PageMetadata getPageMetadata() {
|
||||
return this.pageMetadata;
|
||||
}
|
||||
|
||||
Links getLinks() {
|
||||
return this.links;
|
||||
}
|
||||
|
||||
Map<String, HalFormsTemplate> getTemplates() {
|
||||
return this.templates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the template with the given name.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@JsonIgnore
|
||||
HalFormsTemplate getTemplate(String key) {
|
||||
|
||||
Assert.notNull(key, "Template key must not be null!");
|
||||
|
||||
return this.templates.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default template of the document.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@JsonIgnore
|
||||
HalFormsTemplate getDefaultTemplate() {
|
||||
return getTemplate(HalFormsTemplate.DEFAULT_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
|
||||
if (this == o)
|
||||
return true;
|
||||
if (!(o instanceof HalFormsDocument))
|
||||
return false;
|
||||
HalFormsDocument<?> that = (HalFormsDocument<?>) o;
|
||||
return Objects.equals(this.attributes, that.attributes) && Objects.equals(this.entity, that.entity)
|
||||
&& Objects.equals(this.entities, that.entities) && Objects.equals(this.embedded, that.embedded)
|
||||
&& Objects.equals(this.pageMetadata, that.pageMetadata) && Objects.equals(this.links, that.links)
|
||||
&& Objects.equals(this.templates, that.templates);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
return Objects.hash(this.attributes, this.entity, this.entities, this.embedded, this.pageMetadata, this.links,
|
||||
this.templates);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
|
||||
return "HalFormsDocument(attributes=" + this.attributes + ", entity=" + this.entity + ", entities=" + this.entities
|
||||
+ ", embedded=" + this.embedded + ", pageMetadata=" + this.pageMetadata + ", links=" + this.links
|
||||
+ ", templates=" + this.templates + ")";
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.hateoas.mediatype.hal.forms;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
|
||||
@@ -27,6 +28,7 @@ import org.springframework.hateoas.config.HypermediaMappingInformation;
|
||||
import org.springframework.hateoas.mediatype.MessageResolver;
|
||||
import org.springframework.hateoas.mediatype.hal.CurieProvider;
|
||||
import org.springframework.hateoas.mediatype.hal.HalConfiguration;
|
||||
import org.springframework.hateoas.mediatype.hal.Jackson2HalModule;
|
||||
import org.springframework.hateoas.server.core.DelegatingLinkRelationProvider;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
@@ -39,7 +41,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
* @author Greg Turnquist
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@Configuration
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class HalFormsMediaTypeConfiguration implements HypermediaMappingInformation {
|
||||
|
||||
private final DelegatingLinkRelationProvider relProvider;
|
||||
@@ -49,6 +51,8 @@ class HalFormsMediaTypeConfiguration implements HypermediaMappingInformation {
|
||||
private final MessageResolver resolver;
|
||||
private final AbstractAutowireCapableBeanFactory beanFactory;
|
||||
|
||||
private HalFormsConfiguration resolvedConfiguration;
|
||||
|
||||
public HalFormsMediaTypeConfiguration(DelegatingLinkRelationProvider relProvider,
|
||||
ObjectProvider<CurieProvider> curieProvider, ObjectProvider<HalFormsConfiguration> halFormsConfiguration,
|
||||
ObjectProvider<HalConfiguration> halConfiguration, MessageResolver resolver,
|
||||
@@ -67,6 +71,15 @@ class HalFormsMediaTypeConfiguration implements HypermediaMappingInformation {
|
||||
return new HalFormsLinkDiscoverer();
|
||||
}
|
||||
|
||||
@Bean
|
||||
HalFormsTemplatePropertyWriter halFormsTemplatePropertyWriter() {
|
||||
|
||||
HalFormsConfiguration configuration = getResolvedConfiguration();
|
||||
HalFormsTemplateBuilder builder = new HalFormsTemplateBuilder(configuration, resolver);
|
||||
|
||||
return new HalFormsTemplatePropertyWriter(builder);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.config.HypermediaMappingInformation#configureObjectMapper(com.fasterxml.jackson.databind.ObjectMapper)
|
||||
@@ -74,15 +87,15 @@ class HalFormsMediaTypeConfiguration implements HypermediaMappingInformation {
|
||||
@Override
|
||||
public ObjectMapper configureObjectMapper(ObjectMapper mapper) {
|
||||
|
||||
HalFormsConfiguration configuration = halFormsConfiguration
|
||||
.getIfAvailable(() -> new HalFormsConfiguration(halConfiguration.getIfAvailable(HalConfiguration::new)));
|
||||
HalFormsConfiguration halFormsConfig = getResolvedConfiguration();
|
||||
CurieProvider provider = curieProvider.getIfAvailable(() -> CurieProvider.NONE);
|
||||
|
||||
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
mapper.registerModule(new Jackson2HalFormsModule());
|
||||
mapper.setHandlerInstantiator(new Jackson2HalFormsModule.HalFormsHandlerInstantiator(relProvider,
|
||||
curieProvider.getIfAvailable(() -> CurieProvider.NONE), resolver, configuration, beanFactory));
|
||||
mapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, provider,
|
||||
resolver, halFormsConfig.getHalConfiguration(), beanFactory));
|
||||
|
||||
configuration.customize(mapper);
|
||||
halFormsConfig.customize(mapper);
|
||||
|
||||
return mapper;
|
||||
}
|
||||
@@ -95,4 +108,16 @@ class HalFormsMediaTypeConfiguration implements HypermediaMappingInformation {
|
||||
public List<MediaType> getMediaTypes() {
|
||||
return HypermediaType.HAL_FORMS.getMediaTypes();
|
||||
}
|
||||
|
||||
HalFormsConfiguration getResolvedConfiguration() {
|
||||
|
||||
Supplier<HalFormsConfiguration> defaultConfig = () -> new HalFormsConfiguration(
|
||||
halConfiguration.getIfAvailable(HalConfiguration::new));
|
||||
|
||||
if (resolvedConfiguration == null) {
|
||||
this.resolvedConfiguration = halFormsConfiguration.getIfAvailable(defaultConfig);
|
||||
}
|
||||
|
||||
return resolvedConfiguration;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ import org.springframework.util.StringUtils;
|
||||
* @since 1.3
|
||||
* @soundtrack The Chicks - March March (Gaslighter)
|
||||
*/
|
||||
public class HalFormsPropertyFactory {
|
||||
class HalFormsPropertyFactory {
|
||||
|
||||
private static final Set<HttpMethod> ENTITY_ALTERING_METHODS = EnumSet.of(POST, PUT, PATCH);
|
||||
|
||||
|
||||
@@ -1,326 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017-2021 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.hateoas.mediatype.hal.forms;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.hateoas.CollectionModel;
|
||||
import org.springframework.hateoas.EntityModel;
|
||||
import org.springframework.hateoas.PagedModel;
|
||||
import org.springframework.hateoas.RepresentationModel;
|
||||
import org.springframework.hateoas.mediatype.hal.HalConfiguration;
|
||||
import org.springframework.hateoas.mediatype.hal.HalLinkRelation;
|
||||
import org.springframework.hateoas.mediatype.hal.Jackson2HalModule;
|
||||
import org.springframework.hateoas.mediatype.hal.Jackson2HalModule.EmbeddedMapper;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.BeanProperty;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
|
||||
import com.fasterxml.jackson.databind.ser.ContainerSerializer;
|
||||
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
|
||||
|
||||
/**
|
||||
* Collection of components needed to serialize a HAL-FORMS document.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
class HalFormsSerializers {
|
||||
|
||||
static class HalFormsRepresentationModelSerializer extends ContainerSerializer<RepresentationModel<?>>
|
||||
implements ContextualSerializer {
|
||||
|
||||
private static final long serialVersionUID = -4583146321934407153L;
|
||||
|
||||
private final HalFormsTemplateBuilder builder;
|
||||
private final BeanProperty property;
|
||||
|
||||
HalFormsRepresentationModelSerializer(HalFormsTemplateBuilder builder, @Nullable BeanProperty property) {
|
||||
|
||||
super(RepresentationModel.class, false);
|
||||
|
||||
this.builder = builder;
|
||||
this.property = property;
|
||||
}
|
||||
|
||||
HalFormsRepresentationModelSerializer(HalFormsTemplateBuilder customizations) {
|
||||
this(customizations, 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(builder.findTemplates(value));
|
||||
|
||||
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(builder, property);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializer for {@link CollectionModel}.
|
||||
*/
|
||||
static class HalFormsEntityModelSerializer extends ContainerSerializer<EntityModel<?>>
|
||||
implements ContextualSerializer {
|
||||
|
||||
private static final long serialVersionUID = -7912243216469101379L;
|
||||
|
||||
private final HalFormsTemplateBuilder builder;
|
||||
private final BeanProperty property;
|
||||
|
||||
HalFormsEntityModelSerializer(HalFormsTemplateBuilder builder, @Nullable BeanProperty property) {
|
||||
|
||||
super(EntityModel.class, false);
|
||||
|
||||
this.builder = builder;
|
||||
this.property = property;
|
||||
}
|
||||
|
||||
HalFormsEntityModelSerializer(HalFormsTemplateBuilder builder) {
|
||||
this(builder, 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(EntityModel<?> value, JsonGenerator gen, SerializerProvider provider) throws IOException {
|
||||
|
||||
HalFormsDocument<?> doc = HalFormsDocument.forEntity(value.getContent()) //
|
||||
.withLinks(value.getLinks()) //
|
||||
.withTemplates(builder.findTemplates(value));
|
||||
|
||||
provider.findValueSerializer(HalFormsDocument.class, property).serialize(doc, gen, provider);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentType()
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public JavaType getContentType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentSerializer()
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public JsonSerializer<?> getContentSerializer() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#hasSingleElement(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("null")
|
||||
public boolean hasSingleElement(EntityModel<?> resource) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#_withValueTypeSerializer(com.fasterxml.jackson.databind.jsontype.TypeSerializer)
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
@SuppressWarnings("null")
|
||||
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer typeSerializer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.ser.ContextualSerializer#createContextual(com.fasterxml.jackson.databind.SerializerProvider, com.fasterxml.jackson.databind.BeanProperty)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("null")
|
||||
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
|
||||
throws JsonMappingException {
|
||||
return new HalFormsEntityModelSerializer(builder, property);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializer for {@link CollectionModel}
|
||||
*/
|
||||
static class HalFormsCollectionModelSerializer extends ContainerSerializer<CollectionModel<?>>
|
||||
implements ContextualSerializer {
|
||||
|
||||
private static final long serialVersionUID = -3601146866067500734L;
|
||||
|
||||
private final BeanProperty property;
|
||||
private final Jackson2HalModule.EmbeddedMapper embeddedMapper;
|
||||
private final HalFormsTemplateBuilder customizations;
|
||||
private final HalConfiguration configuration;
|
||||
|
||||
HalFormsCollectionModelSerializer(HalFormsTemplateBuilder customizations,
|
||||
Jackson2HalModule.EmbeddedMapper embeddedMapper, HalConfiguration configuration,
|
||||
@Nullable BeanProperty property) {
|
||||
|
||||
super(CollectionModel.class, false);
|
||||
|
||||
this.property = property;
|
||||
this.embeddedMapper = embeddedMapper;
|
||||
this.customizations = customizations;
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
HalFormsCollectionModelSerializer(HalFormsTemplateBuilder customizations,
|
||||
Jackson2HalModule.EmbeddedMapper embeddedMapper, HalConfiguration configuration) {
|
||||
this(customizations, embeddedMapper, configuration, 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(CollectionModel<?> value, JsonGenerator gen, SerializerProvider provider) throws IOException {
|
||||
|
||||
EmbeddedMapper mapper = configuration.isApplyPropertyNamingStrategy() //
|
||||
? embeddedMapper.with(provider.getConfig().getPropertyNamingStrategy()) //
|
||||
: embeddedMapper;
|
||||
|
||||
Map<HalLinkRelation, Object> embeddeds = mapper.map(value);
|
||||
|
||||
HalFormsDocument<?> doc;
|
||||
|
||||
if (value instanceof PagedModel) {
|
||||
|
||||
doc = HalFormsDocument.empty() //
|
||||
.withEmbedded(embeddeds) //
|
||||
.withPageMetadata(((PagedModel<?>) value).getMetadata()) //
|
||||
.withLinks(value.getLinks()) //
|
||||
.withTemplates(customizations.findTemplates(value));
|
||||
|
||||
} else {
|
||||
|
||||
doc = HalFormsDocument.empty() //
|
||||
.withEmbedded(embeddeds) //
|
||||
.withLinks(value.getLinks()) //
|
||||
.withTemplates(customizations.findTemplates(value));
|
||||
}
|
||||
|
||||
provider.findValueSerializer(HalFormsDocument.class, property).serialize(doc, gen, provider);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentType()
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public JavaType getContentType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentSerializer()
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public JsonSerializer<?> getContentSerializer() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#hasSingleElement(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("null")
|
||||
public boolean hasSingleElement(CollectionModel<?> resources) {
|
||||
return resources.getContent().size() == 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.ser.ContainerSerializer#_withValueTypeSerializer(com.fasterxml.jackson.databind.jsontype.TypeSerializer)
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
@SuppressWarnings("null")
|
||||
protected ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer typeSerializer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.ser.ContextualSerializer#createContextual(com.fasterxml.jackson.databind.SerializerProvider, com.fasterxml.jackson.databind.BeanProperty)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("null")
|
||||
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
|
||||
throws JsonMappingException {
|
||||
return new HalFormsCollectionModelSerializer(customizations, embeddedMapper, configuration, property);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.hateoas.mediatype.hal.forms;
|
||||
|
||||
import org.springframework.hateoas.RepresentationModel;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.cfg.MapperConfig;
|
||||
import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
|
||||
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
|
||||
import com.fasterxml.jackson.databind.ser.VirtualBeanPropertyWriter;
|
||||
import com.fasterxml.jackson.databind.util.Annotations;
|
||||
|
||||
/**
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@SuppressWarnings("null")
|
||||
class HalFormsTemplatePropertyWriter extends VirtualBeanPropertyWriter {
|
||||
|
||||
private static final long serialVersionUID = 6271264033606657428L;
|
||||
|
||||
private final HalFormsTemplateBuilder builder;
|
||||
|
||||
/**
|
||||
* @param builder must not be {@literal null}.
|
||||
*/
|
||||
public HalFormsTemplatePropertyWriter(HalFormsTemplateBuilder builder) {
|
||||
|
||||
Assert.notNull(builder, "HalFormsTemplateBuilder must not be null!");
|
||||
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param builder2
|
||||
* @param config
|
||||
* @param declaringClass
|
||||
* @param propDef
|
||||
* @param type
|
||||
*/
|
||||
public HalFormsTemplatePropertyWriter(HalFormsTemplateBuilder builder, MapperConfig<?> config,
|
||||
Annotations annotations, BeanPropertyDefinition propDef, JavaType type) {
|
||||
|
||||
super(propDef, annotations, type);
|
||||
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.ser.VirtualBeanPropertyWriter#value(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider)
|
||||
*/
|
||||
@Override
|
||||
protected Object value(Object bean, JsonGenerator gen, SerializerProvider prov) throws Exception {
|
||||
|
||||
RepresentationModel<?> model = (RepresentationModel<?>) bean;
|
||||
|
||||
return builder.findTemplates(model);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.ser.VirtualBeanPropertyWriter#withConfig(com.fasterxml.jackson.databind.cfg.MapperConfig, com.fasterxml.jackson.databind.introspect.AnnotatedClass, com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition, com.fasterxml.jackson.databind.JavaType)
|
||||
*/
|
||||
@Override
|
||||
public VirtualBeanPropertyWriter withConfig(MapperConfig<?> config, AnnotatedClass declaringClass,
|
||||
BeanPropertyDefinition propDef, JavaType type) {
|
||||
return new HalFormsTemplatePropertyWriter(builder, config, declaringClass.getAnnotations(), propDef, type);
|
||||
}
|
||||
}
|
||||
@@ -15,57 +15,22 @@
|
||||
*/
|
||||
package org.springframework.hateoas.mediatype.hal.forms;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||
import org.springframework.hateoas.CollectionModel;
|
||||
import org.springframework.hateoas.EntityModel;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.Links;
|
||||
import org.springframework.hateoas.PagedModel;
|
||||
import org.springframework.hateoas.RepresentationModel;
|
||||
import org.springframework.hateoas.mediatype.MessageResolver;
|
||||
import org.springframework.hateoas.mediatype.hal.CurieProvider;
|
||||
import org.springframework.hateoas.mediatype.hal.HalConfiguration;
|
||||
import org.springframework.hateoas.mediatype.hal.Jackson2HalModule.EmbeddedMapper;
|
||||
import org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalHandlerInstantiator;
|
||||
import org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListDeserializer;
|
||||
import org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer;
|
||||
import org.springframework.hateoas.mediatype.hal.CollectionModelMixin;
|
||||
import org.springframework.hateoas.mediatype.hal.LinkMixin;
|
||||
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;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.Version;
|
||||
import com.fasterxml.jackson.databind.DeserializationConfig;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.KeyDeserializer;
|
||||
import com.fasterxml.jackson.databind.SerializationConfig;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonAppend;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonAppend.Prop;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.cfg.MapperConfig;
|
||||
import com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase;
|
||||
import com.fasterxml.jackson.databind.introspect.Annotated;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import com.fasterxml.jackson.databind.type.TypeFactory;
|
||||
|
||||
/**
|
||||
* Serialize / deserialize all the parts of HAL-FORMS documents using Jackson.
|
||||
@@ -83,176 +48,16 @@ public class Jackson2HalFormsModule extends SimpleModule {
|
||||
super("hal-forms-module", new Version(1, 0, 0, null, "org.springframework.hateoas", "spring-hateoas"));
|
||||
|
||||
setMixInAnnotation(Link.class, LinkMixin.class);
|
||||
setMixInAnnotation(Links.class, LinksMixin.class);
|
||||
setMixInAnnotation(RepresentationModel.class, RepresentationModelMixin.class);
|
||||
setMixInAnnotation(EntityModel.class, EntityModelMixin.class);
|
||||
setMixInAnnotation(CollectionModel.class, CollectionModelMixin.class);
|
||||
setMixInAnnotation(PagedModel.class, PagedModelMixin.class);
|
||||
setMixInAnnotation(MediaType.class, MediaTypeMixin.class);
|
||||
}
|
||||
|
||||
@JsonSerialize(using = HalLinkListSerializer.class)
|
||||
abstract class LinksMixin {}
|
||||
|
||||
@JsonSerialize(using = HalFormsRepresentationModelSerializer.class)
|
||||
@JsonAppend(
|
||||
props = @Prop(name = "_templates", value = HalFormsTemplatePropertyWriter.class, include = Include.NON_EMPTY))
|
||||
abstract class RepresentationModelMixin extends org.springframework.hateoas.mediatype.hal.RepresentationModelMixin {}
|
||||
|
||||
@JsonSerialize(using = HalFormsEntityModelSerializer.class)
|
||||
abstract class EntityModelMixin<T> extends EntityModel<T> {}
|
||||
|
||||
@JsonSerialize(using = HalFormsCollectionModelSerializer.class)
|
||||
abstract class CollectionModelMixin<T> extends CollectionModel<T> {
|
||||
|
||||
@Override
|
||||
@JsonProperty("_embedded")
|
||||
@JsonInclude(Include.NON_EMPTY)
|
||||
@JsonDeserialize(using = HalFormsCollectionModelDeserializer.class)
|
||||
public abstract Collection<T> getContent();
|
||||
}
|
||||
|
||||
abstract class PagedModelMixin<T> extends PagedModel<T> {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@JsonProperty("page")
|
||||
@JsonInclude(Include.NON_EMPTY)
|
||||
public PageMetadata getMetadata() {
|
||||
return super.getMetadata();
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@JsonDeserialize(using = MediaTypeDeserializer.class)
|
||||
interface MediaTypeMixin {}
|
||||
|
||||
static class HalFormsLinksDeserializer extends ContainerDeserializerBase<Links> {
|
||||
|
||||
private static final long serialVersionUID = -848240531474910385L;
|
||||
|
||||
private final HalLinkListDeserializer delegate = new HalLinkListDeserializer();
|
||||
|
||||
public HalFormsLinksDeserializer() {
|
||||
super(TypeFactory.defaultInstance().constructType(Links.class));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase#getContentDeserializer()
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public JsonDeserializer<Object> getContentDeserializer() {
|
||||
return delegate.getContentDeserializer();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("null")
|
||||
public Links deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
|
||||
return Links.of(delegate.deserialize(p, ctxt));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new HAL-FORMS serializers based on the context.
|
||||
*/
|
||||
public static class HalFormsHandlerInstantiator extends HalHandlerInstantiator {
|
||||
|
||||
private final Map<Class<?>, Object> serializers = new HashMap<>();
|
||||
|
||||
public HalFormsHandlerInstantiator(LinkRelationProvider resolver, CurieProvider curieProvider,
|
||||
MessageResolver accessor, HalFormsConfiguration configuration, AutowireCapableBeanFactory beanFactory) {
|
||||
|
||||
super(resolver, curieProvider, accessor, configuration.getHalConfiguration(), beanFactory);
|
||||
|
||||
HalConfiguration halConfiguration = configuration.getHalConfiguration();
|
||||
EmbeddedMapper mapper = new EmbeddedMapper(resolver, curieProvider,
|
||||
halConfiguration.isEnforceEmbeddedCollections());
|
||||
HalFormsTemplateBuilder builder = new HalFormsTemplateBuilder(configuration, accessor);
|
||||
|
||||
this.serializers.put(HalFormsRepresentationModelSerializer.class,
|
||||
new HalFormsRepresentationModelSerializer(builder));
|
||||
this.serializers.put(HalFormsEntityModelSerializer.class, new HalFormsEntityModelSerializer(builder));
|
||||
this.serializers.put(HalFormsCollectionModelSerializer.class,
|
||||
new HalFormsCollectionModelSerializer(builder, mapper, halConfiguration));
|
||||
this.serializers.put(HalLinkListSerializer.class,
|
||||
new HalLinkListSerializer(curieProvider, mapper, accessor, halConfiguration));
|
||||
}
|
||||
|
||||
public HalFormsHandlerInstantiator(LinkRelationProvider relProvider, CurieProvider curieProvider,
|
||||
MessageResolver resolver, AutowireCapableBeanFactory beanFactory) {
|
||||
|
||||
this(relProvider, curieProvider, resolver, beanFactory.getBean(HalFormsConfiguration.class), beanFactory);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object findInstance(Class<?> type) {
|
||||
return this.serializers.get(type);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.hal.Jackson2HalModule.HalHandlerInstantiator#deserializerInstance(com.fasterxml.jackson.databind.DeserializationConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public JsonDeserializer<?> deserializerInstance(DeserializationConfig config, Annotated annotated,
|
||||
Class<?> deserClass) {
|
||||
|
||||
Object jsonDeser = findInstance(deserClass);
|
||||
return jsonDeser != null ? (JsonDeserializer<?>) jsonDeser
|
||||
: super.deserializerInstance(config, annotated, deserClass);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.hal.Jackson2HalModule.HalHandlerInstantiator#keyDeserializerInstance(com.fasterxml.jackson.databind.DeserializationConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public KeyDeserializer keyDeserializerInstance(DeserializationConfig config, Annotated annotated,
|
||||
Class<?> keyDeserClass) {
|
||||
|
||||
Object keyDeser = findInstance(keyDeserClass);
|
||||
return keyDeser != null ? (KeyDeserializer) keyDeser
|
||||
: super.keyDeserializerInstance(config, annotated, keyDeserClass);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.hal.Jackson2HalModule.HalHandlerInstantiator#serializerInstance(com.fasterxml.jackson.databind.SerializationConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public JsonSerializer<?> serializerInstance(SerializationConfig config, Annotated annotated, Class<?> serClass) {
|
||||
|
||||
Object jsonSer = findInstance(serClass);
|
||||
return jsonSer != null ? (JsonSerializer<?>) jsonSer : super.serializerInstance(config, annotated, serClass);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.hal.Jackson2HalModule.HalHandlerInstantiator#typeResolverBuilderInstance(com.fasterxml.jackson.databind.cfg.MapperConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public TypeResolverBuilder<?> typeResolverBuilderInstance(MapperConfig<?> config, Annotated annotated,
|
||||
Class<?> builderClass) {
|
||||
|
||||
Object builder = findInstance(builderClass);
|
||||
return builder != null ? (TypeResolverBuilder<?>) builder
|
||||
: super.typeResolverBuilderInstance(config, annotated, builderClass);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.hal.Jackson2HalModule.HalHandlerInstantiator#typeIdResolverInstance(com.fasterxml.jackson.databind.cfg.MapperConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public TypeIdResolver typeIdResolverInstance(MapperConfig<?> config, Annotated annotated, Class<?> resolverClass) {
|
||||
|
||||
Object resolver = findInstance(resolverClass);
|
||||
return resolver != null ? (TypeIdResolver) resolver
|
||||
: super.typeIdResolverInstance(config, annotated, resolverClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,11 +19,6 @@ import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.springframework.hateoas.mediatype.MessageResolver;
|
||||
import org.springframework.hateoas.mediatype.hal.CurieProvider;
|
||||
import org.springframework.hateoas.mediatype.hal.HalConfiguration;
|
||||
import org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalHandlerInstantiator;
|
||||
import org.springframework.hateoas.server.core.AnnotationLinkRelationProvider;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
@@ -44,16 +39,6 @@ public abstract class AbstractJackson2MarshallingIntegrationTest {
|
||||
mapper = MappingTestUtils.defaultObjectMapper();
|
||||
}
|
||||
|
||||
protected ObjectMapper with(HalConfiguration configuration) {
|
||||
|
||||
ObjectMapper copy = mapper.copy();
|
||||
|
||||
copy.setHandlerInstantiator(new HalHandlerInstantiator(new AnnotationLinkRelationProvider(), CurieProvider.NONE,
|
||||
MessageResolver.DEFAULTS_ONLY, configuration));
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
protected String write(Object object) throws Exception {
|
||||
Writer writer = new StringWriter();
|
||||
mapper.writeValue(writer, object);
|
||||
|
||||
@@ -86,27 +86,45 @@ public class MappingTestUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public RepresentationModel<?> readObject(String filename) {
|
||||
return readObject(filename, RepresentationModel.class);
|
||||
public RepresentationModel<?> readObject(String serialized) {
|
||||
try {
|
||||
return mapper.readValue(serialized, RepresentationModel.class);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T readObject(String filename, Class<T> type) {
|
||||
public RepresentationModel<?> readFile(String filename) {
|
||||
return readFile(filename, RepresentationModel.class);
|
||||
}
|
||||
|
||||
public <T> T readFile(String filename, Class<T> type) {
|
||||
|
||||
TypeFactory factory = mapper.getTypeFactory();
|
||||
JavaType javaType = factory.constructType(type);
|
||||
|
||||
return readObject(filename, javaType);
|
||||
return readFile(filename, javaType);
|
||||
}
|
||||
|
||||
public <S> S readObject(String filename, Class<?> type, Class<?> elementType) {
|
||||
public <S> S readFile(String filename, Class<?> type, Class<?> elementType) {
|
||||
|
||||
TypeFactory factory = mapper.getTypeFactory();
|
||||
JavaType javaType = factory.constructParametricType(type, elementType);
|
||||
|
||||
return readObject(filename, javaType);
|
||||
return readFile(filename, javaType);
|
||||
}
|
||||
|
||||
public <S> S readObject(String filename, JavaType type) {
|
||||
public <S> S readFile(String filename, Class<?> type, Class<?> elementType, Class<?> nested) {
|
||||
|
||||
TypeFactory factory = mapper.getTypeFactory();
|
||||
|
||||
JavaType genericElement = factory.constructParametricType(elementType, nested);
|
||||
JavaType javaType = factory.constructParametricType(type, genericElement);
|
||||
|
||||
return readFile(filename, javaType);
|
||||
}
|
||||
|
||||
public <S> S readFile(String filename, JavaType type) {
|
||||
|
||||
ClassPathResource resource = new ClassPathResource(filename, context);
|
||||
|
||||
@@ -119,7 +137,7 @@ public class MappingTestUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public String readFile(String filename) {
|
||||
public String readFileContent(String filename) {
|
||||
|
||||
ClassPathResource resource = new ClassPathResource(filename, context);
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ import org.springframework.hateoas.PagedModel;
|
||||
import org.springframework.hateoas.RepresentationModel;
|
||||
import org.springframework.hateoas.mediatype.hal.SimplePojo;
|
||||
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
/**
|
||||
@@ -71,7 +70,7 @@ class Jackson2CollectionJsonIntegrationTest {
|
||||
RepresentationModel<?> resourceSupport = new RepresentationModel<>();
|
||||
resourceSupport.add(Link.of("localhost").withSelfRel());
|
||||
|
||||
assertThat(mapper.writeObject(resourceSupport)).isEqualTo(mapper.readFile("resource-support.json"));
|
||||
assertThat(mapper.writeObject(resourceSupport)).isEqualTo(mapper.readFileContent("resource-support.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -80,7 +79,7 @@ class Jackson2CollectionJsonIntegrationTest {
|
||||
RepresentationModel<?> expected = new RepresentationModel<>();
|
||||
expected.add(Link.of("localhost"));
|
||||
|
||||
assertThat(mapper.readObject("resource-support.json")).isEqualTo(expected);
|
||||
assertThat(mapper.readFile("resource-support.json")).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -90,7 +89,7 @@ class Jackson2CollectionJsonIntegrationTest {
|
||||
resourceSupport.add(Link.of("localhost"));
|
||||
resourceSupport.add(Link.of("localhost2").withRel("orders"));
|
||||
|
||||
assertThat(mapper.writeObject(resourceSupport)).isEqualTo(mapper.readFile("resource-support-2.json"));
|
||||
assertThat(mapper.writeObject(resourceSupport)).isEqualTo(mapper.readFileContent("resource-support-2.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -99,7 +98,7 @@ class Jackson2CollectionJsonIntegrationTest {
|
||||
ResourceWithAttributes resource = new ResourceWithAttributes("test value");
|
||||
resource.add(Link.of("localhost").withSelfRel());
|
||||
|
||||
assertThat(mapper.writeObject(resource)).isEqualTo(mapper.readFile("resource-support-3.json"));
|
||||
assertThat(mapper.writeObject(resource)).isEqualTo(mapper.readFileContent("resource-support-3.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -108,7 +107,7 @@ class Jackson2CollectionJsonIntegrationTest {
|
||||
ResourceWithAttributes expected = new ResourceWithAttributes("test value");
|
||||
expected.add(Link.of("localhost").withSelfRel());
|
||||
|
||||
assertThat(mapper.readObject("resource-support-3.json", ResourceWithAttributes.class)).isEqualTo(expected);
|
||||
assertThat(mapper.readFile("resource-support-3.json", ResourceWithAttributes.class)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -118,7 +117,7 @@ class Jackson2CollectionJsonIntegrationTest {
|
||||
expected.add(Link.of("localhost"));
|
||||
expected.add(Link.of("localhost2").withRel("orders"));
|
||||
|
||||
assertThat(mapper.readObject("resource-support-2.json").getLinks()).containsAll(expected.getLinks());
|
||||
assertThat(mapper.readFile("resource-support-2.json").getLinks()).containsAll(expected.getLinks());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -131,7 +130,7 @@ class Jackson2CollectionJsonIntegrationTest {
|
||||
CollectionModel<String> resources = CollectionModel.of(content);
|
||||
resources.add(Link.of("localhost"));
|
||||
|
||||
assertThat(mapper.writeObject(resources)).isEqualTo(mapper.readFile("resources.json"));
|
||||
assertThat(mapper.writeObject(resources)).isEqualTo(mapper.readFileContent("resources.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -144,7 +143,7 @@ class Jackson2CollectionJsonIntegrationTest {
|
||||
CollectionModel<String> expected = CollectionModel.of(content);
|
||||
expected.add(Link.of("localhost"));
|
||||
|
||||
CollectionModel<String> result = mapper.readObject("resources.json", CollectionModel.class, String.class);
|
||||
CollectionModel<String> result = mapper.readFile("resources.json", CollectionModel.class, String.class);
|
||||
|
||||
assertThat(result).isEqualTo(expected);
|
||||
}
|
||||
@@ -153,13 +152,13 @@ class Jackson2CollectionJsonIntegrationTest {
|
||||
void renderResource() {
|
||||
|
||||
assertThat(mapper.writeObject(EntityModel.of("first", Link.of("localhost")))) //
|
||||
.isEqualTo(mapper.readFile("resource.json"));
|
||||
.isEqualTo(mapper.readFileContent("resource.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeResource() {
|
||||
|
||||
EntityModel<String> actual = mapper.readObject("resource.json", EntityModel.class, String.class);
|
||||
EntityModel<String> actual = mapper.readFile("resource.json", EntityModel.class, String.class);
|
||||
|
||||
assertThat(actual).isEqualTo(EntityModel.of("first", Link.of("localhost")));
|
||||
}
|
||||
@@ -175,7 +174,7 @@ class Jackson2CollectionJsonIntegrationTest {
|
||||
resources.add(Link.of("localhost"));
|
||||
resources.add(Link.of("/page/2").withRel("next"));
|
||||
|
||||
assertThat(mapper.writeObject(resources)).isEqualTo(mapper.readFile("resources-with-resource-objects.json"));
|
||||
assertThat(mapper.writeObject(resources)).isEqualTo(mapper.readFileContent("resources-with-resource-objects.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -189,10 +188,8 @@ class Jackson2CollectionJsonIntegrationTest {
|
||||
expected.add(Link.of("localhost"));
|
||||
expected.add(Link.of("/page/2").withRel("next"));
|
||||
|
||||
JavaType entityModel = mapper.getGenericType(EntityModel.class, String.class);
|
||||
JavaType collectionModel = mapper.getGenericType(CollectionModel.class, entityModel);
|
||||
|
||||
CollectionModel<?> actual = mapper.readObject("resources-with-resource-objects.json", collectionModel);
|
||||
CollectionModel<?> actual = mapper.readFile("resources-with-resource-objects.json", CollectionModel.class,
|
||||
EntityModel.class, String.class);
|
||||
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
@@ -208,25 +205,21 @@ class Jackson2CollectionJsonIntegrationTest {
|
||||
resources.add(Link.of("localhost"));
|
||||
resources.add(Link.of("/page/2").withRel("next"));
|
||||
|
||||
assertThat(mapper.writeObject(resources)).isEqualTo(mapper.readFile("resources-simple-pojos.json"));
|
||||
assertThat(mapper.writeObject(resources)).isEqualTo(mapper.readFileContent("resources-simple-pojos.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializesPagedResource() throws Exception {
|
||||
|
||||
assertThat(mapper.writeObject(setupAnnotatedPagedResources())) //
|
||||
.isEqualTo(mapper.readFile("paged-resources.json"));
|
||||
.isEqualTo(mapper.readFileContent("paged-resources.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializesPagedResource() throws Exception {
|
||||
|
||||
JavaType entityModel = mapper.getGenericType(EntityModel.class, SimplePojo.class);
|
||||
JavaType pagedModel = mapper.getGenericType(PagedModel.class, entityModel);
|
||||
|
||||
mapper.readObject("paged-resources.json", pagedModel);
|
||||
|
||||
PagedModel<?> result = mapper.readObject("paged-resources.json", pagedModel);
|
||||
PagedModel<?> result = mapper.readFile("paged-resources.json", PagedModel.class, EntityModel.class,
|
||||
SimplePojo.class);
|
||||
|
||||
assertThat(result).isEqualTo(setupAnnotatedPagedResources());
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ public class HalModelBuilderUnitTest {
|
||||
.build();
|
||||
|
||||
assertThat(this.mapper.writeValueAsString(model))
|
||||
.isEqualTo(contextualMapper.readFile("hal-embedded-author-illustrator.json"));
|
||||
.isEqualTo(contextualMapper.readFileContent("hal-embedded-author-illustrator.json"));
|
||||
}
|
||||
|
||||
@Test // #864
|
||||
@@ -119,7 +119,7 @@ public class HalModelBuilderUnitTest {
|
||||
.forLink(Link.of("/people/john-smith", ILLUSTRATOR_REL)).build();
|
||||
|
||||
assertThat(this.mapper.writeValueAsString(model))
|
||||
.isEqualTo(contextualMapper.readFile("hal-embedded-author-illustrator.json"));
|
||||
.isEqualTo(contextualMapper.readFileContent("hal-embedded-author-illustrator.json"));
|
||||
}
|
||||
|
||||
@Test // #864
|
||||
@@ -130,7 +130,7 @@ public class HalModelBuilderUnitTest {
|
||||
.link(ALAN_WATTS_SELF) //
|
||||
.build();
|
||||
|
||||
assertThat(this.mapper.writeValueAsString(model)).isEqualTo(contextualMapper.readFile("hal-single-item.json"));
|
||||
assertThat(this.mapper.writeValueAsString(model)).isEqualTo(contextualMapper.readFileContent("hal-single-item.json"));
|
||||
}
|
||||
|
||||
@Test // #864
|
||||
@@ -142,7 +142,7 @@ public class HalModelBuilderUnitTest {
|
||||
.build();
|
||||
|
||||
assertThat(this.mapper.writeValueAsString(model)) //
|
||||
.isEqualTo(contextualMapper.readFile("hal-single-item.json"));
|
||||
.isEqualTo(contextualMapper.readFileContent("hal-single-item.json"));
|
||||
}
|
||||
|
||||
@Test // #864
|
||||
@@ -173,7 +173,7 @@ public class HalModelBuilderUnitTest {
|
||||
.build();
|
||||
|
||||
assertThat(this.mapper.writeValueAsString(model))
|
||||
.isEqualTo(contextualMapper.readFile("hal-embedded-collection.json"));
|
||||
.isEqualTo(contextualMapper.readFileContent("hal-embedded-collection.json"));
|
||||
}
|
||||
|
||||
@Test // #864
|
||||
@@ -199,7 +199,7 @@ public class HalModelBuilderUnitTest {
|
||||
.build();
|
||||
|
||||
assertThat(this.mapper.writeValueAsString(model))
|
||||
.isEqualTo(contextualMapper.readFile("hal-embedded-collection.json"));
|
||||
.isEqualTo(contextualMapper.readFileContent("hal-embedded-collection.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -208,7 +208,7 @@ public class HalModelBuilderUnitTest {
|
||||
HalModelBuilder halModelBuilder = halModel();
|
||||
|
||||
assertThat(this.mapper.writeValueAsString(halModelBuilder.build()))
|
||||
.isEqualTo(contextualMapper.readFile("hal-empty.json"));
|
||||
.isEqualTo(contextualMapper.readFileContent("hal-empty.json"));
|
||||
|
||||
halModelBuilder //
|
||||
.entity(halModel() //
|
||||
@@ -218,13 +218,13 @@ public class HalModelBuilderUnitTest {
|
||||
.build());
|
||||
|
||||
assertThat(this.mapper.writeValueAsString(halModelBuilder.build()))
|
||||
.isEqualTo(contextualMapper.readFile("hal-one-thing.json"));
|
||||
.isEqualTo(contextualMapper.readFileContent("hal-one-thing.json"));
|
||||
|
||||
halModelBuilder //
|
||||
.embed(new Product("Alf alarm clock", 19.99), LinkRelation.of("product")).build();
|
||||
|
||||
assertThat(this.mapper.writeValueAsString(halModelBuilder.build()))
|
||||
.isEqualTo(contextualMapper.readFile("hal-two-things.json"));
|
||||
.isEqualTo(contextualMapper.readFileContent("hal-two-things.json"));
|
||||
}
|
||||
|
||||
@Test // #193
|
||||
@@ -239,7 +239,7 @@ public class HalModelBuilderUnitTest {
|
||||
.build();
|
||||
|
||||
assertThat(this.mapper.writeValueAsString(model)) //
|
||||
.isEqualTo(contextualMapper.readFile("hal-multiple-types.json"));
|
||||
.isEqualTo(contextualMapper.readFileContent("hal-multiple-types.json"));
|
||||
}
|
||||
|
||||
@Test // #193
|
||||
@@ -260,7 +260,7 @@ public class HalModelBuilderUnitTest {
|
||||
.build();
|
||||
|
||||
assertThat(this.mapper.writeValueAsString(model))
|
||||
.isEqualTo(contextualMapper.readFile("hal-explicit-and-implicit-relations.json"));
|
||||
.isEqualTo(contextualMapper.readFileContent("hal-explicit-and-implicit-relations.json"));
|
||||
}
|
||||
|
||||
@Test // #175 #864
|
||||
@@ -306,7 +306,7 @@ public class HalModelBuilderUnitTest {
|
||||
}
|
||||
|
||||
assertThat(this.mapper.writeValueAsString(builder.build()))
|
||||
.isEqualTo(contextualMapper.readFile("zoom-hypermedia.json"));
|
||||
.isEqualTo(contextualMapper.readFileContent("zoom-hypermedia.json"));
|
||||
}
|
||||
|
||||
@Test // #864
|
||||
|
||||
@@ -63,7 +63,7 @@ class HalObjectMapperCustomizerTest {
|
||||
void objectMapperCustomizerShouldBeApplied() throws Exception {
|
||||
|
||||
String actualHalJson = this.mockMvc.perform(get("/employees/0")).andReturn().getResponse().getContentAsString();
|
||||
String expectedHalJson = this.mapper.readFile("hal-custom.json");
|
||||
String expectedHalJson = this.mapper.readFileContent("hal-custom.json");
|
||||
|
||||
assertThat(actualHalJson).isEqualTo(expectedHalJson);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.hateoas.mediatype.hal;
|
||||
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.hateoas.LinkRelation;
|
||||
import org.springframework.hateoas.MappingTestUtils;
|
||||
import org.springframework.hateoas.mediatype.MessageResolver;
|
||||
@@ -59,7 +60,8 @@ public class HalTestUtils {
|
||||
|
||||
mapper.registerModule(new Jackson2HalModule());
|
||||
mapper.setHandlerInstantiator(
|
||||
new HalHandlerInstantiator(provider, CurieProvider.NONE, MessageResolver.DEFAULTS_ONLY, configuration));
|
||||
new HalHandlerInstantiator(provider, CurieProvider.NONE, MessageResolver.DEFAULTS_ONLY, configuration,
|
||||
new DefaultListableBeanFactory()));
|
||||
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@@ -16,25 +16,28 @@
|
||||
package org.springframework.hateoas.mediatype.hal.forms;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.hateoas.support.MappingUtils.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.hateoas.IanaLinkRelations;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.MappingTestUtils.ContextualMapper;
|
||||
import org.springframework.hateoas.client.LinkDiscoverer;
|
||||
import org.springframework.hateoas.client.LinkDiscovererUnitTest;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link HalFormsLinkDiscoverer}.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
class HalFormsLinkDiscovererUnitTest extends LinkDiscovererUnitTest {
|
||||
|
||||
static final LinkDiscoverer discoverer = new HalFormsLinkDiscoverer();
|
||||
static final ContextualMapper mapper = ContextualMapper.of(HalFormsLinkDiscovererUnitTest.class, new ObjectMapper());
|
||||
|
||||
/**
|
||||
* @see #314
|
||||
@@ -50,7 +53,7 @@ class HalFormsLinkDiscovererUnitTest extends LinkDiscovererUnitTest {
|
||||
@Test
|
||||
void discoversAllTheLinkAttributes() throws IOException {
|
||||
|
||||
String linkText = read(new ClassPathResource("hal-forms-link.json", getClass()));
|
||||
String linkText = mapper.readFileContent("hal-forms-link.json");
|
||||
|
||||
Link expected = Link.valueOf("</customer/1>;" //
|
||||
+ "rel=\"self\";" //
|
||||
@@ -73,12 +76,7 @@ class HalFormsLinkDiscovererUnitTest extends LinkDiscovererUnitTest {
|
||||
|
||||
@Override
|
||||
protected String getInputString() {
|
||||
|
||||
try {
|
||||
return read(new ClassPathResource("hal-forms-link-discoverer.json", getClass()));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return mapper.readFileContent("hal-forms-link-discoverer.json");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017-2021 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.hateoas.mediatype.hal.forms;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.hateoas.RepresentationModel;
|
||||
import org.springframework.hateoas.mediatype.MessageResolver;
|
||||
import org.springframework.hateoas.mediatype.hal.CurieProvider;
|
||||
import org.springframework.hateoas.server.core.AnnotationLinkRelationProvider;
|
||||
import org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* @author Greg Turnquist
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
class HalFormsMessageConverterUnitTest {
|
||||
|
||||
ObjectMapper mapper;
|
||||
HttpMessageConverter<Object> messageConverter;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
|
||||
this.mapper = new ObjectMapper();
|
||||
this.mapper.registerModule(new Jackson2HalFormsModule());
|
||||
this.mapper.setHandlerInstantiator(
|
||||
new Jackson2HalFormsModule.HalFormsHandlerInstantiator(new AnnotationLinkRelationProvider(), CurieProvider.NONE,
|
||||
MessageResolver.DEFAULTS_ONLY, new HalFormsConfiguration(), new DefaultListableBeanFactory()));
|
||||
|
||||
TypeConstrainedMappingJackson2HttpMessageConverter converter = new TypeConstrainedMappingJackson2HttpMessageConverter(
|
||||
RepresentationModel.class);
|
||||
converter.setObjectMapper(mapper);
|
||||
|
||||
this.messageConverter = converter;
|
||||
}
|
||||
|
||||
@Test
|
||||
void canReadAHalFormsDocumentMessage() throws IOException {
|
||||
|
||||
HttpInputMessage message = new HttpInputMessage() {
|
||||
@Override
|
||||
public InputStream getBody() throws IOException {
|
||||
return new ClassPathResource("reference.json", getClass()).getInputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
return new HttpHeaders();
|
||||
}
|
||||
};
|
||||
|
||||
Object convertedMessage = this.messageConverter.read(HalFormsDocument.class, message);
|
||||
|
||||
assertThat(convertedMessage).isInstanceOf(HalFormsDocument.class);
|
||||
|
||||
HalFormsDocument<?> halFormsDocument = (HalFormsDocument<?>) convertedMessage;
|
||||
|
||||
assertThat(halFormsDocument.getLinks()).hasSize(2);
|
||||
assertThat(halFormsDocument.getLinks()).extracting(Link::getHref).containsExactly("/employees", "/employees/1");
|
||||
|
||||
assertThat(halFormsDocument.getTemplates().size()).isEqualTo(1);
|
||||
assertThat(halFormsDocument.getTemplates().keySet()).containsExactly("default");
|
||||
assertThat(halFormsDocument.getTemplates().get("default").getContentType()).isEqualTo("application/hal+json");
|
||||
assertThat(halFormsDocument.getTemplates().get("default").getHttpMethod()).isEqualTo(HttpMethod.GET);
|
||||
assertThat(halFormsDocument.getTemplates().get("default").getMethod())
|
||||
.isEqualTo(HttpMethod.GET.toString().toLowerCase());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
void canWriteAHalFormsDocumentMessage() throws IOException {
|
||||
|
||||
HalFormsProperty property = HalFormsProperty.named("my-name")//
|
||||
.withReadOnly(true) //
|
||||
.withValue("my-value") //
|
||||
.withPrompt("my-prompt") //
|
||||
.withRegex("my-regex") //
|
||||
.withRequired(true);
|
||||
|
||||
HalFormsTemplate template = HalFormsTemplate.forMethod(HttpMethod.GET) //
|
||||
.withTitle("HAL-FORMS unit test") //
|
||||
.withContentType(MediaTypes.HAL_JSON) //
|
||||
.andProperty(property); //
|
||||
|
||||
HalFormsDocument expected = HalFormsDocument.empty() //
|
||||
.andLink(Link.of("/employees").withRel("collection")) //
|
||||
.andLink(Link.of("/employees/1").withSelfRel())//
|
||||
.andTemplate("foo", template);
|
||||
|
||||
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
|
||||
HttpOutputMessage convertedMessage = new HttpOutputMessage() {
|
||||
@Override
|
||||
public OutputStream getBody() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
return new HttpHeaders();
|
||||
}
|
||||
};
|
||||
|
||||
this.messageConverter.write(expected, MediaTypes.HAL_FORMS_JSON, convertedMessage);
|
||||
|
||||
assertThat(this.mapper.readValue(stream.toString(), HalFormsDocument.class)).isEqualTo(expected);
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@ class HalFormsObjectMapperCustomizerTest {
|
||||
|
||||
String actualHalFormsJson = mockMvc.perform(get("/employees/0")).andReturn().getResponse()
|
||||
.getContentAsString();
|
||||
String expectedHalFormsJson = mapper.readFile("hal-forms-custom.json");
|
||||
String expectedHalFormsJson = mapper.readFileContent("hal-forms-custom.json");
|
||||
|
||||
assertThat(actualHalFormsJson).isEqualTo(expectedHalFormsJson);
|
||||
}
|
||||
|
||||
@@ -29,26 +29,20 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.hateoas.Links;
|
||||
import org.springframework.hateoas.MappingTestUtils;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.hateoas.config.EnableHypermediaSupport;
|
||||
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
|
||||
import org.springframework.hateoas.mediatype.hal.HalConfiguration;
|
||||
import org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalLinkListSerializer;
|
||||
import org.springframework.hateoas.support.WebMvcEmployeeController;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@@ -121,7 +115,7 @@ class HalFormsWebMvcIntegrationTest {
|
||||
@Test
|
||||
void createNewEmployee() throws Exception {
|
||||
|
||||
String specBasedJson = MappingTestUtils.createMapper(getClass()).readFile("new-employee.json");
|
||||
String specBasedJson = MappingTestUtils.createMapper(getClass()).readFileContent("new-employee.json");
|
||||
|
||||
this.mockMvc.perform(post("/employees") //
|
||||
.content(specBasedJson) //
|
||||
@@ -145,18 +139,9 @@ class HalFormsWebMvcIntegrationTest {
|
||||
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(configurationClass)) {
|
||||
|
||||
HalFormsMediaTypeConfiguration mediaTypeConfiguration = context.getBean(HalFormsMediaTypeConfiguration.class);
|
||||
ObjectMapper mapper = mediaTypeConfiguration.configureObjectMapper(new ObjectMapper());
|
||||
HalFormsConfiguration actual = mediaTypeConfiguration.getResolvedConfiguration();
|
||||
|
||||
assertThatCode(() -> {
|
||||
|
||||
JsonSerializer<Object> serializer = mapper.getSerializerProviderInstance() //
|
||||
.findValueSerializer(Links.class);
|
||||
|
||||
assertThat(serializer).isInstanceOfSatisfying(HalLinkListSerializer.class, it -> {
|
||||
assertThat(ReflectionTestUtils.getField(serializer, "halConfiguration")).isSameAs(configuration);
|
||||
});
|
||||
|
||||
}).doesNotThrowAnyException();
|
||||
assertThat(actual.getHalConfiguration()).isSameAs(configuration);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
@@ -37,36 +38,40 @@ import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.context.support.StaticMessageSource;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.hateoas.AbstractJackson2MarshallingIntegrationTest;
|
||||
import org.springframework.hateoas.CollectionModel;
|
||||
import org.springframework.hateoas.EntityModel;
|
||||
import org.springframework.hateoas.IanaLinkRelations;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.Links;
|
||||
import org.springframework.hateoas.MappingTestUtils;
|
||||
import org.springframework.hateoas.MappingTestUtils.ContextualMapper;
|
||||
import org.springframework.hateoas.PagedModel;
|
||||
import org.springframework.hateoas.RepresentationModel;
|
||||
import org.springframework.hateoas.UriTemplate;
|
||||
import org.springframework.hateoas.config.HateoasConfiguration;
|
||||
import org.springframework.hateoas.mediatype.Affordances;
|
||||
import org.springframework.hateoas.mediatype.MessageResolver;
|
||||
import org.springframework.hateoas.mediatype.hal.CurieProvider;
|
||||
import org.springframework.hateoas.mediatype.hal.DefaultCurieProvider;
|
||||
import org.springframework.hateoas.mediatype.hal.HalConfiguration;
|
||||
import org.springframework.hateoas.mediatype.hal.HalTestUtils;
|
||||
import org.springframework.hateoas.mediatype.hal.Jackson2HalModule;
|
||||
import org.springframework.hateoas.mediatype.hal.SimpleAnnotatedPojo;
|
||||
import org.springframework.hateoas.mediatype.hal.SimplePojo;
|
||||
import org.springframework.hateoas.mediatype.hal.forms.Jackson2HalFormsModule.HalFormsHandlerInstantiator;
|
||||
import org.springframework.hateoas.server.LinkRelationProvider;
|
||||
import org.springframework.hateoas.server.core.AnnotationLinkRelationProvider;
|
||||
import org.springframework.hateoas.server.core.DelegatingLinkRelationProvider;
|
||||
import org.springframework.hateoas.server.core.EmbeddedWrappers;
|
||||
import org.springframework.hateoas.support.EmployeeResource;
|
||||
import org.springframework.hateoas.support.MappingUtils;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
|
||||
import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
@@ -77,26 +82,34 @@ import com.jayway.jsonpath.PathNotFoundException;
|
||||
* @author Greg Turnquist
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegrationTest {
|
||||
class Jackson2HalFormsIntegrationTest {
|
||||
|
||||
static final Links PAGINATION_LINKS = Links.of( //
|
||||
Link.of("foo", IanaLinkRelations.NEXT), //
|
||||
Link.of("bar", IanaLinkRelations.PREV) //
|
||||
);
|
||||
|
||||
final LinkRelationProvider provider = new DelegatingLinkRelationProvider(new AnnotationLinkRelationProvider(),
|
||||
HalTestUtils.DefaultLinkRelationProvider.INSTANCE);
|
||||
final Consumer<ObjectMapper> configurer = it -> {
|
||||
|
||||
it.registerModule(new Jackson2HalFormsModule());
|
||||
it.configure(SerializationFeature.INDENT_OUTPUT, true);
|
||||
};
|
||||
|
||||
final ContextualMapper mapper = MappingTestUtils.createMapper(Jackson2HalFormsIntegrationTest.class,
|
||||
configurer.andThen(it -> {
|
||||
|
||||
GenericApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
HalFormsMediaTypeConfiguration.class, HateoasConfiguration.class);
|
||||
|
||||
it.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(provider, CurieProvider.NONE,
|
||||
MessageResolver.DEFAULTS_ONLY, new HalConfiguration(), context.getAutowireCapableBeanFactory()));
|
||||
}));
|
||||
|
||||
@BeforeEach
|
||||
void setUpModule() {
|
||||
|
||||
LocaleContextHolder.setLocale(Locale.US);
|
||||
|
||||
LinkRelationProvider provider = new DelegatingLinkRelationProvider(new AnnotationLinkRelationProvider(),
|
||||
HalTestUtils.DefaultLinkRelationProvider.INSTANCE);
|
||||
|
||||
mapper.registerModule(new Jackson2HalFormsModule());
|
||||
mapper.setHandlerInstantiator(new HalFormsHandlerInstantiator( //
|
||||
provider, CurieProvider.NONE, MessageResolver.DEFAULTS_ONLY, new HalFormsConfiguration(),
|
||||
new DefaultListableBeanFactory()));
|
||||
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -105,8 +118,8 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
RepresentationModel<?> resourceSupport = new RepresentationModel<>();
|
||||
resourceSupport.add(Link.of("localhost"));
|
||||
|
||||
assertThat(write(resourceSupport))
|
||||
.isEqualTo(MappingUtils.read(new ClassPathResource("single-link-reference.json", getClass())));
|
||||
assertThat(mapper.writeObject(resourceSupport))
|
||||
.isEqualTo(mapper.readFileContent("single-link-reference.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -115,8 +128,7 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
RepresentationModel<?> expected = new RepresentationModel<>();
|
||||
expected.add(Link.of("localhost"));
|
||||
|
||||
assertThat(read(MappingUtils.read(new ClassPathResource("single-link-reference.json", getClass())),
|
||||
RepresentationModel.class)).isEqualTo(expected);
|
||||
assertThat(mapper.readFile("single-link-reference.json")).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -126,8 +138,8 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
resourceSupport.add(Link.of("localhost"));
|
||||
resourceSupport.add(Link.of("localhost2"));
|
||||
|
||||
assertThat(write(resourceSupport))
|
||||
.isEqualTo(MappingUtils.read(new ClassPathResource("list-link-reference.json", getClass())));
|
||||
assertThat(mapper.writeObject(resourceSupport))
|
||||
.isEqualTo(mapper.readFileContent("list-link-reference.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -137,8 +149,7 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
expected.add(Link.of("localhost"));
|
||||
expected.add(Link.of("localhost2"));
|
||||
|
||||
assertThat(read(MappingUtils.read(new ClassPathResource("list-link-reference.json", getClass())),
|
||||
RepresentationModel.class)).isEqualTo(expected);
|
||||
assertThat(mapper.readFile("list-link-reference.json", RepresentationModel.class)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -154,8 +165,8 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
|
||||
resource.add(link);
|
||||
|
||||
assertThat(write(resource))
|
||||
.isEqualTo(MappingUtils.read(new ClassPathResource("employee-resource-support.json", getClass())));
|
||||
assertThat(mapper.writeObject(resource))
|
||||
.isEqualTo(mapper.readFileContent("employee-resource-support.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -163,20 +174,17 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
|
||||
EntityModel<SimplePojo> resource = EntityModel.of(new SimplePojo("test1", 1), Link.of("localhost"));
|
||||
|
||||
assertThat(write(resource))
|
||||
.isEqualTo(MappingUtils.read(new ClassPathResource("simple-resource-unwrapped.json", getClass())));
|
||||
assertThat(mapper.writeObject(resource))
|
||||
.isEqualTo(mapper.readFileContent("simple-resource-unwrapped.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializesResource() throws IOException {
|
||||
|
||||
EntityModel<SimplePojo> expected = EntityModel.of(new SimplePojo("test1", 1), Link.of("localhost"));
|
||||
EntityModel<SimplePojo> result = mapper.readFile("simple-resource-unwrapped.json", EntityModel.class,
|
||||
SimplePojo.class);
|
||||
|
||||
EntityModel<SimplePojo> result = mapper.readValue(
|
||||
MappingUtils.read(new ClassPathResource("simple-resource-unwrapped.json", getClass())),
|
||||
mapper.getTypeFactory().constructParametricType(EntityModel.class, SimplePojo.class));
|
||||
|
||||
assertThat(result).isEqualTo(expected);
|
||||
assertThat(result).isEqualTo(EntityModel.of(new SimplePojo("test1", 1), Link.of("localhost")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -189,8 +197,8 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
CollectionModel<String> resources = CollectionModel.of(content);
|
||||
resources.add(Link.of("localhost"));
|
||||
|
||||
assertThat(write(resources))
|
||||
.isEqualTo(MappingUtils.read(new ClassPathResource("simple-embedded-resource-reference.json", getClass())));
|
||||
assertThat(mapper.writeObject(resources))
|
||||
.isEqualTo(mapper.readFileContent("simple-embedded-resource-reference.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -203,9 +211,8 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
CollectionModel<String> expected = CollectionModel.of(content);
|
||||
expected.add(Link.of("localhost"));
|
||||
|
||||
CollectionModel<String> result = mapper.readValue(
|
||||
MappingUtils.read(new ClassPathResource("simple-embedded-resource-reference.json", getClass())),
|
||||
mapper.getTypeFactory().constructParametricType(CollectionModel.class, String.class));
|
||||
CollectionModel<String> result = mapper.readFile("simple-embedded-resource-reference.json", CollectionModel.class,
|
||||
String.class);
|
||||
|
||||
assertThat(result).isEqualTo(expected);
|
||||
|
||||
@@ -220,8 +227,8 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
CollectionModel<EntityModel<SimplePojo>> resources = CollectionModel.of(content);
|
||||
resources.add(Link.of("localhost"));
|
||||
|
||||
assertThat(write(resources))
|
||||
.isEqualTo(MappingUtils.read(new ClassPathResource("single-embedded-resource-reference.json", getClass())));
|
||||
assertThat(mapper.writeObject(resources))
|
||||
.isEqualTo(mapper.readFileContent("single-embedded-resource-reference.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -233,10 +240,8 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
CollectionModel<EntityModel<SimplePojo>> expected = CollectionModel.of(content);
|
||||
expected.add(Link.of("localhost"));
|
||||
|
||||
CollectionModel<EntityModel<SimplePojo>> result = mapper.readValue(
|
||||
MappingUtils.read(new ClassPathResource("single-embedded-resource-reference.json", getClass())),
|
||||
mapper.getTypeFactory().constructParametricType(CollectionModel.class,
|
||||
mapper.getTypeFactory().constructParametricType(EntityModel.class, SimplePojo.class)));
|
||||
CollectionModel<EntityModel<SimplePojo>> result = mapper.readFile("single-embedded-resource-reference.json",
|
||||
CollectionModel.class, EntityModel.class, SimplePojo.class);
|
||||
|
||||
assertThat(result).isEqualTo(expected);
|
||||
}
|
||||
@@ -247,8 +252,8 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
CollectionModel<EntityModel<SimplePojo>> resources = setupResources();
|
||||
resources.add(Link.of("localhost"));
|
||||
|
||||
assertThat(write(resources))
|
||||
.isEqualTo(MappingUtils.read(new ClassPathResource("multiple-resource-resources.json", getClass())));
|
||||
assertThat(mapper.writeObject(resources))
|
||||
.isEqualTo(mapper.readFileContent("multiple-resource-resources.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -257,10 +262,8 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
CollectionModel<EntityModel<SimplePojo>> expected = setupResources();
|
||||
expected.add(Link.of("localhost"));
|
||||
|
||||
CollectionModel<EntityModel<SimplePojo>> result = mapper.readValue(
|
||||
MappingUtils.read(new ClassPathResource("multiple-resource-resources.json", getClass())),
|
||||
mapper.getTypeFactory().constructParametricType(CollectionModel.class,
|
||||
mapper.getTypeFactory().constructParametricType(EntityModel.class, SimplePojo.class)));
|
||||
CollectionModel<EntityModel<SimplePojo>> result = mapper.readFile("multiple-resource-resources.json",
|
||||
CollectionModel.class, EntityModel.class, SimplePojo.class);
|
||||
|
||||
assertThat(result).isEqualTo(expected);
|
||||
}
|
||||
@@ -274,8 +277,7 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
CollectionModel<EntityModel<SimpleAnnotatedPojo>> resources = CollectionModel.of(content);
|
||||
resources.add(Link.of("localhost"));
|
||||
|
||||
assertThat(write(resources))
|
||||
.isEqualTo(MappingUtils.read(new ClassPathResource("annotated-resource-resources.json", getClass())));
|
||||
assertThat(mapper.writeObject(resources)).isEqualTo(mapper.readFileContent("annotated-resource-resources.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -287,43 +289,43 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
CollectionModel<EntityModel<SimpleAnnotatedPojo>> expected = CollectionModel.of(content);
|
||||
expected.add(Link.of("localhost"));
|
||||
|
||||
CollectionModel<EntityModel<SimpleAnnotatedPojo>> result = mapper.readValue(
|
||||
MappingUtils.read(new ClassPathResource("annotated-resource-resources.json", getClass())),
|
||||
mapper.getTypeFactory().constructParametricType(CollectionModel.class,
|
||||
mapper.getTypeFactory().constructParametricType(EntityModel.class, SimpleAnnotatedPojo.class)));
|
||||
CollectionModel<EntityModel<SimpleAnnotatedPojo>> result = mapper.readFile("annotated-resource-resources.json",
|
||||
CollectionModel.class, EntityModel.class, SimpleAnnotatedPojo.class);
|
||||
|
||||
assertThat(result).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializesMultipleAnnotatedResourceResourcesAsEmbedded() throws Exception {
|
||||
assertThat(write(setupAnnotatedResources()))
|
||||
.isEqualTo(MappingUtils.read(new ClassPathResource("annotated-embedded-resources-reference.json", getClass())));
|
||||
assertThat(mapper.writeObject(setupAnnotatedResources()))
|
||||
.isEqualTo(mapper.readFileContent("annotated-embedded-resources-reference.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializesMultipleAnnotatedResourceResourcesAsEmbedded() throws Exception {
|
||||
|
||||
CollectionModel<EntityModel<SimpleAnnotatedPojo>> result = mapper.readValue(
|
||||
MappingUtils.read(new ClassPathResource("annotated-embedded-resources-reference.json", getClass())),
|
||||
mapper.getTypeFactory().constructParametricType(CollectionModel.class,
|
||||
mapper.getTypeFactory().constructParametricType(EntityModel.class, SimpleAnnotatedPojo.class)));
|
||||
mapper.readFile("annotated-embedded-resources-reference.json",
|
||||
CollectionModel.class, EntityModel.class, SimpleAnnotatedPojo.class);
|
||||
|
||||
CollectionModel<EntityModel<SimpleAnnotatedPojo>> result = mapper.readFile(
|
||||
"annotated-embedded-resources-reference.json", CollectionModel.class, EntityModel.class,
|
||||
SimpleAnnotatedPojo.class);
|
||||
|
||||
assertThat(result).isEqualTo(setupAnnotatedResources());
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializesPagedResource() throws Exception {
|
||||
assertThat(write(setupAnnotatedPagedResources()))
|
||||
.isEqualTo(MappingUtils.read(new ClassPathResource("annotated-paged-resources.json", getClass())));
|
||||
|
||||
assertThat(mapper.writeObject(setupAnnotatedPagedResources()))
|
||||
.isEqualTo(mapper.readFileContent("annotated-paged-resources.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializesPagedResource() throws Exception {
|
||||
PagedModel<EntityModel<SimpleAnnotatedPojo>> result = mapper.readValue(
|
||||
MappingUtils.read(new ClassPathResource("annotated-paged-resources.json", getClass())),
|
||||
mapper.getTypeFactory().constructParametricType(PagedModel.class,
|
||||
mapper.getTypeFactory().constructParametricType(EntityModel.class, SimpleAnnotatedPojo.class)));
|
||||
|
||||
PagedModel<EntityModel<SimpleAnnotatedPojo>> result = mapper.readFile("annotated-paged-resources.json",
|
||||
PagedModel.class, EntityModel.class, SimpleAnnotatedPojo.class);
|
||||
|
||||
assertThat(result).isEqualTo(setupAnnotatedPagedResources());
|
||||
}
|
||||
@@ -334,16 +336,16 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
CollectionModel<Object> resources = CollectionModel.of(Collections.emptySet(), Link.of("foo"),
|
||||
Link.of("bar", "myrel"));
|
||||
|
||||
assertThat(getCuriedObjectMapper().writeValueAsString(resources))
|
||||
.isEqualTo(MappingUtils.read(new ClassPathResource("curied-document.json", getClass())));
|
||||
assertThat(getCuriedObjectMapper().writeObject(resources))
|
||||
.isEqualTo(mapper.readFileContent("curied-document.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void doesNotRenderCuriesIfNoLinkIsPresent() throws Exception {
|
||||
|
||||
CollectionModel<Object> resources = CollectionModel.of(Collections.emptySet());
|
||||
assertThat(getCuriedObjectMapper().writeValueAsString(resources))
|
||||
.isEqualTo(MappingUtils.read(new ClassPathResource("empty-document.json", getClass())));
|
||||
assertThat(getCuriedObjectMapper().writeObject(resources))
|
||||
.isEqualTo(mapper.readFileContent("empty-document.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -352,8 +354,8 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
CollectionModel<Object> resources = CollectionModel.of(Collections.emptySet());
|
||||
resources.add(Link.of("foo"));
|
||||
|
||||
assertThat(getCuriedObjectMapper().writeValueAsString(resources))
|
||||
.isEqualTo(MappingUtils.read(new ClassPathResource("single-non-curie-document.json", getClass())));
|
||||
assertThat(getCuriedObjectMapper().writeObject(resources))
|
||||
.isEqualTo(mapper.readFileContent("single-non-curie-document.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -362,7 +364,7 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
RepresentationModel<?> support = new RepresentationModel<>();
|
||||
support.add(Link.of("/foo{?bar}", "search"));
|
||||
|
||||
assertThat(write(support)).isEqualTo(MappingUtils.read(new ClassPathResource("link-template.json", getClass())));
|
||||
assertThat(mapper.writeObject(support)).isEqualTo(mapper.readFileContent("link-template.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -378,8 +380,8 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
}
|
||||
};
|
||||
|
||||
assertThat(getCuriedObjectMapper(provider).writeValueAsString(resources))
|
||||
.isEqualTo(MappingUtils.read(new ClassPathResource("multiple-curies-document.json", getClass())));
|
||||
assertThat(getCuriedObjectMapper(provider).writeObject(resources))
|
||||
.isEqualTo(mapper.readFileContent("multiple-curies-document.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -392,8 +394,7 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
|
||||
CollectionModel<Object> resources = CollectionModel.of(values);
|
||||
|
||||
assertThat(write(resources))
|
||||
.isEqualTo(MappingUtils.read(new ClassPathResource("empty-embedded-pojos.json", getClass())));
|
||||
assertThat(mapper.writeObject(resources)).isEqualTo(mapper.readFileContent("empty-embedded-pojos.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -415,13 +416,13 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
RepresentationModel<?> original = new RepresentationModel<>();
|
||||
original.add(Link.of("/orders{?id}", "order"));
|
||||
|
||||
String serialized = mapper.writeValueAsString(original);
|
||||
String serialized = mapper.writeObject(original);
|
||||
|
||||
String expected = "{\n \"_links\" : {\n \"order\" : {\n \"href\" : \"/orders{?id}\",\n \"templated\" : true\n }\n }\n}";
|
||||
|
||||
assertThat(serialized).isEqualTo(expected);
|
||||
|
||||
RepresentationModel<?> deserialized = mapper.readValue(serialized, RepresentationModel.class);
|
||||
RepresentationModel<?> deserialized = mapper.readObject(serialized);
|
||||
|
||||
assertThat(deserialized).isEqualTo(original);
|
||||
}
|
||||
@@ -443,12 +444,12 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
.toLink();
|
||||
|
||||
EntityModel<HalFormsPayload> model = EntityModel.of(new HalFormsPayload(), link);
|
||||
ObjectMapper mapper = getCuriedObjectMapper(CurieProvider.NONE, source);
|
||||
ContextualMapper mapper = getCuriedObjectMapper(CurieProvider.NONE, source);
|
||||
|
||||
assertThatCode(() -> {
|
||||
|
||||
String promptString = JsonPath.compile("$._templates.default.properties[0].prompt") //
|
||||
.read(mapper.writeValueAsString(model));
|
||||
.read(mapper.writeObject(model));
|
||||
|
||||
assertThat(promptString).isEqualTo("Vorname");
|
||||
|
||||
@@ -474,12 +475,12 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
.toLink();
|
||||
|
||||
EntityModel<HalFormsPayload> model = EntityModel.of(new HalFormsPayload(), link);
|
||||
ObjectMapper mapper = getCuriedObjectMapper(CurieProvider.NONE, source);
|
||||
ContextualMapper mapper = getCuriedObjectMapper(CurieProvider.NONE, source);
|
||||
|
||||
assertThatCode(() -> {
|
||||
|
||||
String promptString = JsonPath.compile("$._templates.default.title") //
|
||||
.read(mapper.writeValueAsString(model));
|
||||
.read(mapper.writeObject(model));
|
||||
|
||||
assertThat(promptString).isEqualTo("Template title");
|
||||
|
||||
@@ -545,12 +546,12 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
.toLink();
|
||||
|
||||
EntityModel<HalFormsPayload> model = EntityModel.of(new HalFormsPayload(), link);
|
||||
ObjectMapper mapper = getCuriedObjectMapper(CurieProvider.NONE, source);
|
||||
ContextualMapper mapper = getCuriedObjectMapper(CurieProvider.NONE, source);
|
||||
|
||||
assertThatCode(() -> {
|
||||
|
||||
String promptString = JsonPath.compile("$._templates.default.properties[0].placeholder") //
|
||||
.read(mapper.writeValueAsString(model));
|
||||
.read(mapper.writeObject(model));
|
||||
|
||||
assertThat(promptString).isEqualTo("Property placeholder");
|
||||
|
||||
@@ -559,8 +560,7 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
|
||||
private void assertThatPathDoesNotExist(Object toMarshall, String path) throws Exception {
|
||||
|
||||
ObjectMapper mapper = getCuriedObjectMapper();
|
||||
String json = mapper.writeValueAsString(toMarshall);
|
||||
String json = getCuriedObjectMapper().writeObject(toMarshall);
|
||||
|
||||
assertThatExceptionOfType(PathNotFoundException.class) //
|
||||
.isThrownBy(() -> JsonPath.compile(path).read(json));
|
||||
@@ -568,8 +568,7 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
|
||||
private void assertValueForPath(Object toMarshall, String path, Object expected) throws Exception {
|
||||
|
||||
ObjectMapper mapper = getCuriedObjectMapper();
|
||||
String json = mapper.writeValueAsString(toMarshall);
|
||||
String json = getCuriedObjectMapper().writeObject(toMarshall);
|
||||
|
||||
Object actual = JsonPath.compile(path).read(json);
|
||||
|
||||
@@ -585,13 +584,13 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
StaticMessageSource messageSource = new StaticMessageSource();
|
||||
messageSource.addMessage(resourceBundleKey, Locale.US, "Foobar's title!");
|
||||
|
||||
ObjectMapper objectMapper = getCuriedObjectMapper(CurieProvider.NONE, messageSource);
|
||||
ContextualMapper objectMapper = getCuriedObjectMapper(CurieProvider.NONE, messageSource);
|
||||
|
||||
RepresentationModel<?> resource = new RepresentationModel<>();
|
||||
resource.add(Link.of("target", "ns:foobar"));
|
||||
|
||||
assertThat(objectMapper.writeValueAsString(resource))
|
||||
.isEqualTo(MappingUtils.read(new ClassPathResource("link-with-title.json", getClass())));
|
||||
assertThat(objectMapper.writeObject(resource))
|
||||
.isEqualTo(objectMapper.readFileContent("link-with-title.json"));
|
||||
}
|
||||
|
||||
private static CollectionModel<EntityModel<SimplePojo>> setupResources() {
|
||||
@@ -621,32 +620,34 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
return PagedModel.of(content, new PagedModel.PageMetadata(2, 0, 4), PAGINATION_LINKS);
|
||||
}
|
||||
|
||||
private ObjectMapper getCuriedObjectMapper() {
|
||||
|
||||
private ContextualMapper getCuriedObjectMapper() {
|
||||
return getCuriedObjectMapper(new DefaultCurieProvider("foo", UriTemplate.of("http://localhost:8080/rels/{rel}")));
|
||||
}
|
||||
|
||||
private ObjectMapper getCuriedObjectMapper(CurieProvider provider) {
|
||||
private ContextualMapper getCuriedObjectMapper(CurieProvider provider) {
|
||||
return getCuriedObjectMapper(provider, null);
|
||||
}
|
||||
|
||||
private ObjectMapper getCuriedObjectMapper(CurieProvider provider, @Nullable MessageSource messageSource) {
|
||||
private ContextualMapper getCuriedObjectMapper(CurieProvider provider, @Nullable MessageSource messageSource) {
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
MessageResolver resolver = MessageResolver.of(messageSource);
|
||||
|
||||
mapper.registerModule(new Jackson2HalFormsModule());
|
||||
mapper.setHandlerInstantiator(new HalFormsHandlerInstantiator(new AnnotationLinkRelationProvider(), provider,
|
||||
MessageResolver.of(messageSource), new HalFormsConfiguration(), new DefaultListableBeanFactory()));
|
||||
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
|
||||
mapper.setSerializationInclusion(Include.NON_NULL);
|
||||
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
|
||||
HalFormsTemplateBuilder builder = new HalFormsTemplateBuilder(new HalFormsConfiguration(), resolver);
|
||||
factory.registerSingleton("foobar", new HalFormsTemplatePropertyWriter(builder));
|
||||
|
||||
return mapper;
|
||||
return MappingTestUtils.createMapper(Jackson2HalFormsIntegrationTest.class, configurer.andThen(it -> {
|
||||
it.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(this.provider, provider,
|
||||
resolver, new HalConfiguration(), factory));
|
||||
}));
|
||||
}
|
||||
|
||||
@JsonAutoDetect(getterVisibility = Visibility.PUBLIC_ONLY)
|
||||
public static class HalFormsPayload {
|
||||
private @Getter String firstname;
|
||||
}
|
||||
|
||||
@JsonAutoDetect(getterVisibility = Visibility.PUBLIC_ONLY)
|
||||
public static class Jsr303Sample {
|
||||
|
||||
private String firstname;
|
||||
@@ -671,6 +672,7 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra
|
||||
}
|
||||
}
|
||||
|
||||
@JsonAutoDetect(getterVisibility = Visibility.PUBLIC_ONLY)
|
||||
public static class UnwrappedExampleElement {
|
||||
private @Getter String firstname;
|
||||
}
|
||||
|
||||
@@ -18,13 +18,17 @@ package org.springframework.hateoas.support;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
import org.springframework.hateoas.RepresentationModel;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@Data
|
||||
@Getter(onMethod = @__(@JsonProperty))
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@AllArgsConstructor
|
||||
public class EmployeeResource extends RepresentationModel<EmployeeResource> {
|
||||
@@ -32,7 +36,7 @@ public class EmployeeResource extends RepresentationModel<EmployeeResource> {
|
||||
private String name;
|
||||
|
||||
public EmployeeResource(EmployeeResource employeeResource) {
|
||||
|
||||
|
||||
this.name = employeeResource.getName();
|
||||
add(employeeResource.getLinks());
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"name" : "Frodo Baggins",
|
||||
"_links" : {
|
||||
"self" : {
|
||||
"href" : "/employees/1"
|
||||
@@ -12,6 +13,5 @@
|
||||
"type" : "text"
|
||||
} ]
|
||||
}
|
||||
},
|
||||
"name" : "Frodo Baggins"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user