#340 - Polishing.
Moved AffordanceModelFactory into core package as it's SPI. Switched to Spring Factories lookup of implementation classes so that we avoid a package dependency between the MVC package and the media type specific packages. Removed reference to MediaType from AffordanceModelFactory to AffordanceModel so that a factory can even provide models for different MediaTypes (i.e. different flavors of the same one, e.g. HAL Forms for JSON and XML). Also removed addAffordanceModel(…) from Affordance to not force the implementations into mutability. Made most of the affordance building API types package protected. HalFormsAffordanceModel now uses MethodParameters abstraction to simplify model parsing code. Tweaked HAL forms model to work with factory methods for required properties and wither methods to add optional properties. Tweaked and inlined mixin types in Jackson module for HAL forms. Slight API polishing on Link to make sure Affordance collecting methods are not named with…. Tweaked Lombok setup to use all caps for logger constants. Removed deprecation warnings in Jackson2HalModule.
This commit is contained in:
@@ -207,9 +207,8 @@ public class Jackson2HalModule extends SimpleModule {
|
||||
JavaType valueType = typeFactory.constructCollectionType(ArrayList.class, Object.class);
|
||||
JavaType mapType = typeFactory.constructMapType(HashMap.class, keyType, valueType);
|
||||
|
||||
MapSerializer serializer = MapSerializer.construct(Collections.emptySet(), mapType, true, null,
|
||||
provider.findKeySerializer(keyType, null), new OptionalListJackson2Serializer(property, halConfiguration),
|
||||
null);
|
||||
MapSerializer serializer = MapSerializer.construct(Collections.<String> emptySet(), mapType, true, null,
|
||||
provider.findKeySerializer(keyType, null), new OptionalListJackson2Serializer(property, halConfiguration), null);
|
||||
|
||||
serializer.serialize(sortedLinks, jgen, provider);
|
||||
}
|
||||
@@ -402,14 +401,6 @@ public class Jackson2HalModule extends SimpleModule {
|
||||
private final Map<Class<?>, JsonSerializer<Object>> serializers;
|
||||
private final HalConfiguration halConfiguration;
|
||||
|
||||
public OptionalListJackson2Serializer() {
|
||||
this(null, new HalConfiguration().withRenderSingleLinks(RenderSingleLinks.AS_SINGLE));
|
||||
}
|
||||
|
||||
public OptionalListJackson2Serializer(BeanProperty property) {
|
||||
this(property, new HalConfiguration().withRenderSingleLinks(RenderSingleLinks.AS_SINGLE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link OptionalListJackson2Serializer} using the given {@link BeanProperty}.
|
||||
*
|
||||
@@ -527,7 +518,7 @@ public class Jackson2HalModule extends SimpleModule {
|
||||
@Override
|
||||
public JsonSerializer<?> createContextual(SerializerProvider provider, BeanProperty property)
|
||||
throws JsonMappingException {
|
||||
return new OptionalListJackson2Serializer(property);
|
||||
return new OptionalListJackson2Serializer(property, halConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -573,7 +564,7 @@ public class Jackson2HalModule extends SimpleModule {
|
||||
while (!JsonToken.END_OBJECT.equals(jp.nextToken())) {
|
||||
|
||||
if (!JsonToken.FIELD_NAME.equals(jp.getCurrentToken())) {
|
||||
throw new JsonParseException(jp, "Expected relation name", jp.getCurrentLocation());
|
||||
throw new JsonParseException(jp, "Expected relation name");
|
||||
}
|
||||
|
||||
// save the relation in case the link does not contain it
|
||||
@@ -649,7 +640,7 @@ public class Jackson2HalModule extends SimpleModule {
|
||||
while (!JsonToken.END_OBJECT.equals(jp.nextToken())) {
|
||||
|
||||
if (!JsonToken.FIELD_NAME.equals(jp.getCurrentToken())) {
|
||||
throw new JsonParseException(jp, "Expected relation name", jp.getCurrentLocation());
|
||||
throw new JsonParseException(jp, "Expected relation name");
|
||||
}
|
||||
|
||||
if (JsonToken.START_ARRAY.equals(jp.nextToken())) {
|
||||
|
||||
@@ -30,7 +30,7 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
* @author Oliver Gierke
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@JsonIgnoreProperties({"rel", "media"})
|
||||
@JsonIgnoreProperties({ "rel", "media", "affordances", "template" })
|
||||
public abstract class LinkMixin extends Link {
|
||||
|
||||
private static final long serialVersionUID = 4720588561299667409L;
|
||||
@@ -66,7 +66,7 @@ public abstract class LinkMixin extends Link {
|
||||
@Override
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public abstract String getDeprecation();
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.Link#isTemplate()
|
||||
|
||||
@@ -15,20 +15,29 @@
|
||||
*/
|
||||
package org.springframework.hateoas.hal.forms;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.hateoas.Affordance;
|
||||
import org.springframework.hateoas.AffordanceModel;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.hateoas.core.DummyInvocationUtils.MethodInvocation;
|
||||
import org.springframework.hateoas.core.MethodParameters;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
|
||||
@@ -37,42 +46,28 @@ import org.springframework.web.util.UriComponents;
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class HalFormsAffordanceModel implements AffordanceModel {
|
||||
@Slf4j
|
||||
class HalFormsAffordanceModel implements AffordanceModel {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(HalFormsAffordanceModel.class);
|
||||
private static final List<HttpMethod> METHODS_FOR_INPUT_DETECTTION = Arrays.asList(HttpMethod.POST, HttpMethod.PUT,
|
||||
HttpMethod.PATCH);
|
||||
|
||||
/**
|
||||
* Details about the affordance's
|
||||
*/
|
||||
private final UriComponents components;
|
||||
|
||||
/**
|
||||
* Is this required/not required?
|
||||
*/
|
||||
private final boolean required;
|
||||
|
||||
/**
|
||||
* {@link Map} of property names and their types associated with the incoming request body.
|
||||
*/
|
||||
private final Map<String, Class<?>> properties;
|
||||
|
||||
public HalFormsAffordanceModel(Affordance affordance, MethodInvocation invocationValue, UriComponents components) {
|
||||
|
||||
this.components = components;
|
||||
this.required = determineRequired(affordance.getHttpMethod());
|
||||
|
||||
this.properties = new TreeMap<String, Class<?>>();
|
||||
|
||||
if (affordance.getHttpMethod().equalsIgnoreCase("POST") ||
|
||||
affordance.getHttpMethod().equalsIgnoreCase("PUT") ||
|
||||
affordance.getHttpMethod().equalsIgnoreCase("PATCH")) {
|
||||
|
||||
determineAffordanceInputs(invocationValue.getMethod());
|
||||
}
|
||||
this.properties = METHODS_FOR_INPUT_DETECTTION.contains(affordance.getHttpMethod()) //
|
||||
? determineAffordanceInputs(invocationValue.getMethod()) //
|
||||
: Collections.<String, Class<?>> emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the details of the Spring MVC method's {@link RequestBody} into a collection of {@link HalFormsProperty}s.
|
||||
* Transform the details of the Spring MVC method's {@link RequestBody} into a collection of
|
||||
* {@link HalFormsProperty}s.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@@ -80,20 +75,42 @@ public class HalFormsAffordanceModel implements AffordanceModel {
|
||||
|
||||
List<HalFormsProperty> halFormsProperties = new ArrayList<HalFormsProperty>();
|
||||
|
||||
for (Map.Entry<String, Class<?>> entry : this.properties.entrySet()) {
|
||||
halFormsProperties.add(new HalFormsProperty(entry.getKey(), null, null, null, null, false, this.required, false));
|
||||
for (Entry<String, Class<?>> entry : this.properties.entrySet()) {
|
||||
|
||||
HalFormsProperty property = HalFormsProperty//
|
||||
.named(entry.getKey())//
|
||||
.withRequired(required);
|
||||
|
||||
halFormsProperties.add(property);
|
||||
}
|
||||
|
||||
return halFormsProperties;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return components.getPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up the path of the {@link UriComponents}.
|
||||
* Returns whether the affordance is pointing to the same path as the given one.
|
||||
*
|
||||
* @param path must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public String getPath() {
|
||||
return this.components.getPath();
|
||||
public boolean hasPath(String path) {
|
||||
|
||||
Assert.notNull(path, "Path must not be null!");
|
||||
|
||||
return getPath().equals(path);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.AffordanceModel#getMediaType()
|
||||
*/
|
||||
@Override
|
||||
public Collection<MediaType> getMediaTypes() {
|
||||
return Collections.singleton(MediaTypes.HAL_FORMS_JSON);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,48 +119,45 @@ public class HalFormsAffordanceModel implements AffordanceModel {
|
||||
* @param httpMethod - string representation of an HTTP method, e.g. GET, POST, etc.
|
||||
* @return
|
||||
*/
|
||||
private boolean determineRequired(String httpMethod) {
|
||||
|
||||
if (httpMethod.equalsIgnoreCase("POST") || httpMethod.equalsIgnoreCase("PUT")) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
private boolean determineRequired(HttpMethod httpMethod) {
|
||||
return Arrays.asList(HttpMethod.POST, HttpMethod.PUT).contains(httpMethod);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Look at the inputs for a Spring MVC controller method to decide the {@link Affordance}'s properties.
|
||||
*
|
||||
* @param method - {@link Method} of the Spring MVC controller tied to this affordance
|
||||
*/
|
||||
private void determineAffordanceInputs(Method method) {
|
||||
private Map<String, Class<?>> determineAffordanceInputs(Method method) {
|
||||
|
||||
if (method == null) {
|
||||
return;
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
log.debug("Gathering details about " + method.getDeclaringClass().getCanonicalName() + "." + method.getName());
|
||||
LOG.debug("Gathering details about " + method.getDeclaringClass().getCanonicalName() + "." + method.getName());
|
||||
|
||||
for (int i = 0; i < method.getParameterTypes().length; i++) {
|
||||
Map<String, Class<?>> properties = new TreeMap<String, Class<?>>();
|
||||
MethodParameters parameters = new MethodParameters(method);
|
||||
|
||||
for (Annotation annotation : method.getParameterAnnotations()[i]) {
|
||||
for (MethodParameter parameter : parameters.getParametersWith(RequestBody.class)) {
|
||||
|
||||
if (annotation.annotationType().equals(RequestBody.class)) {
|
||||
Class<?> parameterType = parameter.getParameterType();
|
||||
|
||||
log.debug("\tRequest body: " + method.getParameterTypes()[i].getCanonicalName() + "(");
|
||||
LOG.debug("\tRequest body: " + parameterType.getCanonicalName() + "(");
|
||||
|
||||
for (PropertyDescriptor descriptor : BeanUtils.getPropertyDescriptors(method.getParameterTypes()[i])) {
|
||||
for (PropertyDescriptor descriptor : BeanUtils.getPropertyDescriptors(parameterType)) {
|
||||
|
||||
if (!descriptor.getName().equals("class")) {
|
||||
log.debug("\t\t" + descriptor.getPropertyType().getCanonicalName() + " " + descriptor.getName());
|
||||
this.properties.put(descriptor.getName(), descriptor.getPropertyType());
|
||||
}
|
||||
}
|
||||
log.debug(")");
|
||||
if (!descriptor.getName().equals("class")) {
|
||||
|
||||
LOG.debug("\t\t" + descriptor.getPropertyType().getCanonicalName() + " " + descriptor.getName());
|
||||
properties.put(descriptor.getName(), descriptor.getPropertyType());
|
||||
}
|
||||
}
|
||||
|
||||
LOG.debug(")");
|
||||
}
|
||||
|
||||
log.debug("Assembled " + this.toString());
|
||||
LOG.debug("Assembled " + this.toString());
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,10 @@
|
||||
*/
|
||||
package org.springframework.hateoas.hal.forms;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import org.springframework.hateoas.Affordance;
|
||||
import org.springframework.hateoas.AffordanceModel;
|
||||
import org.springframework.hateoas.AffordanceModelFactory;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.hateoas.core.AffordanceModelFactory;
|
||||
import org.springframework.hateoas.core.DummyInvocationUtils.MethodInvocation;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
@@ -29,14 +27,26 @@ import org.springframework.web.util.UriComponents;
|
||||
* Factory for creating {@link HalFormsAffordanceModel}s.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Getter
|
||||
public class HalFormsAffordanceModelFactory extends AffordanceModelFactory {
|
||||
class HalFormsAffordanceModelFactory implements AffordanceModelFactory {
|
||||
|
||||
private final MediaType mediaType = MediaTypes.HAL_FORMS_JSON;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.AffordanceModelFactory#getAffordanceModel(org.springframework.hateoas.Affordance, org.springframework.hateoas.core.DummyInvocationUtils.MethodInvocation, org.springframework.web.util.UriComponents)
|
||||
*/
|
||||
@Override
|
||||
public AffordanceModel getAffordanceModel(Affordance affordance, MethodInvocation invocationValue, UriComponents components) {
|
||||
public AffordanceModel getAffordanceModel(Affordance affordance, MethodInvocation invocationValue,
|
||||
UriComponents components) {
|
||||
return new HalFormsAffordanceModel(affordance, invocationValue, components);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(MediaType mediaType) {
|
||||
return MediaTypes.HAL_FORMS_JSON.equals(mediaType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,9 +38,12 @@ import com.fasterxml.jackson.databind.type.TypeFactory;
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class HalFormsDeserializers {
|
||||
class HalFormsDeserializers {
|
||||
|
||||
static class HalFormsResourcesDeserializer extends ContainerDeserializerBase<List<Object>> implements ContextualDeserializer {
|
||||
static class HalFormsResourcesDeserializer extends ContainerDeserializerBase<List<Object>>
|
||||
implements ContextualDeserializer {
|
||||
|
||||
private static final long serialVersionUID = -7325599536381465624L;
|
||||
|
||||
private JavaType contentType;
|
||||
|
||||
@@ -65,7 +68,7 @@ public class HalFormsDeserializers {
|
||||
while (!JsonToken.END_OBJECT.equals(jp.nextToken())) {
|
||||
|
||||
if (!JsonToken.FIELD_NAME.equals(jp.getCurrentToken())) {
|
||||
throw new JsonParseException("Expected relation name", jp.getCurrentLocation());
|
||||
throw new JsonParseException(jp, "Expected relation name");
|
||||
}
|
||||
|
||||
if (JsonToken.START_ARRAY.equals(jp.nextToken())) {
|
||||
@@ -93,7 +96,8 @@ public class HalFormsDeserializers {
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
|
||||
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
|
||||
throws JsonMappingException {
|
||||
|
||||
if (property != null) {
|
||||
JavaType vc = property.getType().getContentType();
|
||||
|
||||
@@ -15,24 +15,28 @@
|
||||
*/
|
||||
package org.springframework.hateoas.hal.forms;
|
||||
|
||||
import static com.fasterxml.jackson.annotation.JsonInclude.*;
|
||||
import static org.springframework.hateoas.hal.Jackson2HalModule.*;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Singular;
|
||||
import lombok.Value;
|
||||
import lombok.experimental.Wither;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.PagedResources;
|
||||
import org.springframework.hateoas.hal.Jackson2HalModule.HalLinkListDeserializer;
|
||||
import org.springframework.hateoas.hal.Jackson2HalModule.HalLinkListSerializer;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
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;
|
||||
@@ -44,69 +48,154 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
*
|
||||
* @author Dietrich Schulten
|
||||
* @author Greg Turnquist
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Data
|
||||
@Builder(builderMethodName = "halFormsDocument")
|
||||
@Value
|
||||
@Wither
|
||||
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
|
||||
@JsonPropertyOrder({ "resource", "resources", "embedded", "links", "templates", "metadata" })
|
||||
public class HalFormsDocument<T> {
|
||||
|
||||
@JsonUnwrapped
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
@JsonUnwrapped //
|
||||
@JsonInclude(Include.NON_NULL) //
|
||||
@Wither(AccessLevel.PRIVATE) //
|
||||
private T resource;
|
||||
|
||||
@JsonIgnore
|
||||
@JsonInclude(Include.NON_EMPTY)
|
||||
@JsonIgnore //
|
||||
@Wither(AccessLevel.PRIVATE) //
|
||||
private Collection<T> resources;
|
||||
|
||||
@JsonProperty("_embedded")
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
@JsonProperty("_embedded") //
|
||||
@JsonInclude(Include.NON_EMPTY) //
|
||||
private Map<String, Object> embedded;
|
||||
|
||||
@JsonProperty("page")
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
@JsonProperty("page") //
|
||||
@JsonInclude(Include.NON_NULL) //
|
||||
private PagedResources.PageMetadata pageMetadata;
|
||||
|
||||
@Singular private List<Link> links;
|
||||
@Singular //
|
||||
@JsonProperty("_links") //
|
||||
@JsonInclude(Include.NON_EMPTY) //
|
||||
@JsonSerialize(using = HalLinkListSerializer.class) //
|
||||
@JsonDeserialize(using = HalLinkListDeserializer.class) //
|
||||
private List<Link> links;
|
||||
|
||||
@Singular private Map<String, HalFormsTemplate> templates;
|
||||
@Singular //
|
||||
@JsonProperty("_templates") //
|
||||
@JsonInclude(Include.NON_EMPTY) //
|
||||
private Map<String, HalFormsTemplate> templates;
|
||||
|
||||
HalFormsDocument(T resource, Collection<T> resources, Map<String, Object> embedded,
|
||||
PagedResources.PageMetadata pageMetadata, List<Link> links, Map<String, HalFormsTemplate> templates) {
|
||||
|
||||
this.resource = resource;
|
||||
this.resources = resources;
|
||||
this.embedded = embedded;
|
||||
this.pageMetadata = pageMetadata;
|
||||
this.links = links;
|
||||
this.templates = templates;
|
||||
private HalFormsDocument() {
|
||||
this(null, null, Collections.<String, Object> emptyMap(), null, Collections.<Link> emptyList(),
|
||||
Collections.<String, HalFormsTemplate> emptyMap());
|
||||
}
|
||||
|
||||
HalFormsDocument() {
|
||||
this(null, null, null, null, new ArrayList<Link>(), new HashMap<String, HalFormsTemplate>());
|
||||
/**
|
||||
* Creates a new {@link HalFormsDocument} for the given resource.
|
||||
*
|
||||
* @param resource can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static <T> HalFormsDocument<T> forResource(T resource) {
|
||||
return new HalFormsDocument<T>().withResource(resource);
|
||||
}
|
||||
|
||||
@JsonProperty("_links")
|
||||
@JsonInclude(Include.NON_EMPTY)
|
||||
@JsonSerialize(using = HalLinkListSerializer.class)
|
||||
@JsonDeserialize(using = HalLinkListDeserializer.class)
|
||||
public List<Link> getLinks() {
|
||||
return this.links;
|
||||
/**
|
||||
* returns a new {@link HalFormsDocument} for the given resources.
|
||||
*
|
||||
* @param resources must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static <T> HalFormsDocument<T> forResources(Collection<T> resources) {
|
||||
|
||||
Assert.notNull(resources, "Resources must not be null!");
|
||||
|
||||
return new HalFormsDocument<T>().withResources(resources);
|
||||
}
|
||||
|
||||
@JsonProperty("_templates")
|
||||
@JsonInclude(Include.NON_EMPTY)
|
||||
public Map<String, HalFormsTemplate> getTemplates() {
|
||||
return this.templates;
|
||||
/**
|
||||
* Creates a new empty {@link HalFormsDocument}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static HalFormsDocument<?> empty() {
|
||||
return new HalFormsDocument<Object>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default template of the document.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@JsonIgnore
|
||||
public HalFormsTemplate getTemplate() {
|
||||
public HalFormsTemplate getDefaultTemplate() {
|
||||
return getTemplate(HalFormsTemplate.DEFAULT_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the template with the given name.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@JsonIgnore
|
||||
public HalFormsTemplate getTemplate(String key) {
|
||||
|
||||
Assert.notNull(key, "Template key must not be null!");
|
||||
|
||||
return this.templates.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given {@link Link} to the current document.
|
||||
*
|
||||
* @param link must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public HalFormsDocument<T> andLink(Link link) {
|
||||
|
||||
Assert.notNull(link, "Link must not be null!");
|
||||
|
||||
List<Link> links = new ArrayList<Link>(this.links);
|
||||
links.add(link);
|
||||
|
||||
return new HalFormsDocument<T>(resource, resources, embedded, pageMetadata, links, templates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given {@link HalFormsTemplate} to the current document.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @param template must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public 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<String, HalFormsTemplate>(this.templates);
|
||||
templates.put(name, template);
|
||||
|
||||
return new HalFormsDocument<T>(resource, resources, embedded, pageMetadata, links, templates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given value as embedded one.
|
||||
*
|
||||
* @param key must not be {@literal null} or empty.
|
||||
* @param value must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public HalFormsDocument<T> andEmbedded(String key, Object value) {
|
||||
|
||||
Assert.notNull(key, "Embedded key must not be null!");
|
||||
Assert.notNull(value, "Embedded value must not be null!");
|
||||
|
||||
Map<String, Object> embedded = new HashMap<String, Object>(this.embedded);
|
||||
embedded.put(key, value);
|
||||
|
||||
return new HalFormsDocument<T>(resource, resources, embedded, pageMetadata, links, templates);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,14 +32,13 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
/**
|
||||
* A message converter that converts any object into a HAL-FORMS document before bundling up
|
||||
* as an {@link HttpOutputMessage}, or that converts any incoming {@link HttpInputMessage} into
|
||||
* an object.
|
||||
* A message converter that converts any object into a HAL-FORMS document before bundling up as an
|
||||
* {@link HttpOutputMessage}, or that converts any incoming {@link HttpInputMessage} into an object.
|
||||
*
|
||||
* @author Dietrich Schulten
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class HalFormsMessageConverter extends AbstractHttpMessageConverter<Object> {
|
||||
class HalFormsMessageConverter extends AbstractHttpMessageConverter<Object> {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@@ -47,6 +46,7 @@ public class HalFormsMessageConverter extends AbstractHttpMessageConverter<Objec
|
||||
|
||||
this.objectMapper = objectMapper;
|
||||
this.objectMapper.registerModule(new Jackson2HalFormsModule());
|
||||
|
||||
setSupportedMediaTypes(Arrays.asList(MediaTypes.HAL_FORMS_JSON));
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ public class HalFormsMessageConverter extends AbstractHttpMessageConverter<Objec
|
||||
* @see org.springframework.http.converter.AbstractHttpMessageConverter#supports(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
protected boolean supports(final Class<?> clazz) {
|
||||
protected boolean supports(Class<?> clazz) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -64,14 +64,14 @@ public class HalFormsMessageConverter extends AbstractHttpMessageConverter<Objec
|
||||
* @see org.springframework.http.converter.AbstractHttpMessageConverter#readInternal(java.lang.Class, org.springframework.http.HttpInputMessage)
|
||||
*/
|
||||
@Override
|
||||
protected Object readInternal(final Class<? extends Object> clazz, final HttpInputMessage inputMessage)
|
||||
protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage)
|
||||
throws IOException, HttpMessageNotReadableException {
|
||||
|
||||
return this.objectMapper.readValue(inputMessage.getBody(), clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeInternal(final Object t, final HttpOutputMessage outputMessage)
|
||||
protected void writeInternal(Object t, HttpOutputMessage outputMessage)
|
||||
throws IOException, HttpMessageNotWritableException {
|
||||
|
||||
JsonGenerator jsonGenerator = objectMapper.getFactory().createGenerator(outputMessage.getBody(), JsonEncoding.UTF8);
|
||||
@@ -88,5 +88,4 @@ public class HalFormsMessageConverter extends AbstractHttpMessageConverter<Objec
|
||||
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,32 +15,33 @@
|
||||
*/
|
||||
package org.springframework.hateoas.hal.forms;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
import lombok.Value;
|
||||
import lombok.experimental.Wither;
|
||||
|
||||
import org.springframework.hateoas.AffordanceModel;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
|
||||
/**
|
||||
* Describe a parameter for the associated state transition in a HAL-FORMS document.
|
||||
* A {@link HalFormsTemplate} may contain a list of {@link HalFormsProperty}s
|
||||
* Describe a parameter for the associated state transition in a HAL-FORMS document. A {@link HalFormsTemplate} may
|
||||
* contain a list of {@link HalFormsProperty}s
|
||||
*
|
||||
* @see http://mamund.site44.com/misc/hal-forms/
|
||||
*/
|
||||
@JsonInclude(Include.NON_DEFAULT)
|
||||
@Value
|
||||
@AllArgsConstructor
|
||||
@Wither
|
||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
|
||||
public class HalFormsProperty {
|
||||
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* readOnly uses {@link Boolean} not {@literal boolean}, because if {@literal null}, the element won't be rendered
|
||||
*/
|
||||
private @Wither(AccessLevel.PRIVATE) @NonNull String name;
|
||||
private Boolean readOnly;
|
||||
|
||||
private String value;
|
||||
private String prompt;
|
||||
private String regex;
|
||||
@@ -49,9 +50,15 @@ public class HalFormsProperty {
|
||||
private boolean multi;
|
||||
|
||||
/**
|
||||
* Default constructor to support Jackson.
|
||||
* Creates a new {@link HalFormsProperty} with the given name.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
HalFormsProperty() {
|
||||
this(null, null, null, null, null, false, false, false);
|
||||
public static HalFormsProperty named(String name) {
|
||||
|
||||
Assert.hasText(name, "Property name must not be null or empty!");
|
||||
|
||||
return new HalFormsProperty().withName(name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,13 +48,15 @@ import com.fasterxml.jackson.databind.ser.ContextualSerializer;
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class HalFormsSerializers {
|
||||
class HalFormsSerializers {
|
||||
|
||||
/**
|
||||
* Serializer for {@link Resources}.
|
||||
*/
|
||||
static class HalFormsResourceSerializer extends ContainerSerializer<Resource<?>> implements ContextualSerializer {
|
||||
|
||||
private static final long serialVersionUID = -7912243216469101379L;
|
||||
|
||||
private final BeanProperty property;
|
||||
|
||||
HalFormsResourceSerializer(BeanProperty property) {
|
||||
@@ -70,15 +72,11 @@ public class HalFormsSerializers {
|
||||
@Override
|
||||
public void serialize(Resource<?> value, JsonGenerator gen, SerializerProvider provider) throws IOException {
|
||||
|
||||
HalFormsDocument<?> doc = HalFormsDocument.<Object> halFormsDocument()
|
||||
.resource(value.getContent())
|
||||
.links(value.getLinks())
|
||||
.templates(findTemplates(value))
|
||||
.build();
|
||||
HalFormsDocument<?> doc = HalFormsDocument.forResource(value.getContent()) //
|
||||
.withLinks(value.getLinks()) //
|
||||
.withTemplates(findTemplates(value));
|
||||
|
||||
provider
|
||||
.findValueSerializer(HalFormsDocument.class, property)
|
||||
.serialize(doc, gen, provider);
|
||||
provider.findValueSerializer(HalFormsDocument.class, property).serialize(doc, gen, provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -102,7 +100,8 @@ public class HalFormsSerializers {
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
|
||||
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
|
||||
throws JsonMappingException {
|
||||
return new HalFormsResourceSerializer(property);
|
||||
}
|
||||
}
|
||||
@@ -112,12 +111,15 @@ public class HalFormsSerializers {
|
||||
*/
|
||||
static class HalFormsResourcesSerializer extends ContainerSerializer<Resources<?>> implements ContextualSerializer {
|
||||
|
||||
private static final long serialVersionUID = -3601146866067500734L;
|
||||
|
||||
private final BeanProperty property;
|
||||
private final Jackson2HalModule.EmbeddedMapper embeddedMapper;
|
||||
|
||||
HalFormsResourcesSerializer(BeanProperty property, Jackson2HalModule.EmbeddedMapper embeddedMapper) {
|
||||
|
||||
super(Resources.class, false);
|
||||
|
||||
this.property = property;
|
||||
this.embeddedMapper = embeddedMapper;
|
||||
}
|
||||
@@ -135,25 +137,21 @@ public class HalFormsSerializers {
|
||||
|
||||
if (value instanceof PagedResources) {
|
||||
|
||||
doc = HalFormsDocument.<Object> halFormsDocument()
|
||||
.embedded(embeddeds)
|
||||
.pageMetadata(((PagedResources) value).getMetadata())
|
||||
.links(value.getLinks())
|
||||
.templates(findTemplates(value))
|
||||
.build();
|
||||
doc = HalFormsDocument.empty() //
|
||||
.withEmbedded(embeddeds) //
|
||||
.withPageMetadata(((PagedResources<?>) value).getMetadata()) //
|
||||
.withLinks(value.getLinks()) //
|
||||
.withTemplates(findTemplates(value));
|
||||
|
||||
} else {
|
||||
|
||||
doc = HalFormsDocument.<Object> halFormsDocument()
|
||||
.embedded(embeddeds)
|
||||
.pageMetadata(null)
|
||||
.links(value.getLinks())
|
||||
.templates(findTemplates(value))
|
||||
.build();
|
||||
doc = HalFormsDocument.empty() //
|
||||
.withEmbedded(embeddeds) //
|
||||
.withLinks(value.getLinks()) //
|
||||
.withTemplates(findTemplates(value));
|
||||
}
|
||||
|
||||
provider
|
||||
.findValueSerializer(HalFormsDocument.class, property)
|
||||
.serialize(doc, gen, provider);
|
||||
provider.findValueSerializer(HalFormsDocument.class, property).serialize(doc, gen, provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -177,7 +175,8 @@ public class HalFormsSerializers {
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
|
||||
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
|
||||
throws JsonMappingException {
|
||||
return new HalFormsResourcesSerializer(property, embeddedMapper);
|
||||
}
|
||||
}
|
||||
@@ -195,25 +194,19 @@ public class HalFormsSerializers {
|
||||
if (resource.hasLink(Link.REL_SELF)) {
|
||||
for (Affordance affordance : resource.getLink(Link.REL_SELF).map(Link::getAffordances).orElse(Collections.emptyList())) {
|
||||
|
||||
HalFormsAffordanceModel model =
|
||||
(HalFormsAffordanceModel) affordance.getAffordanceModel(MediaTypes.HAL_FORMS_JSON);
|
||||
HalFormsAffordanceModel model = affordance.getAffordanceModel(MediaTypes.HAL_FORMS_JSON);
|
||||
|
||||
if (!affordance.getHttpMethod().equals(HttpMethod.GET.toString())) {
|
||||
if (!affordance.getHttpMethod().equals(HttpMethod.GET)) {
|
||||
|
||||
validate(resource, affordance, model);
|
||||
|
||||
HalFormsTemplate template = new HalFormsTemplate();
|
||||
template.setHttpMethod(HttpMethod.valueOf(affordance.getHttpMethod()));
|
||||
template.setProperties(model.getProperties());
|
||||
HalFormsTemplate template = HalFormsTemplate.forMethod(affordance.getHttpMethod()) //
|
||||
.withProperties(model.getProperties());
|
||||
|
||||
/**
|
||||
* First template in HAL-FORMS is "default".
|
||||
*/
|
||||
if (templates.isEmpty()) {
|
||||
templates.put("default", template);
|
||||
} else {
|
||||
templates.put(affordance.getName(), template);
|
||||
}
|
||||
templates.put(templates.isEmpty() ? "default" : affordance.getName(), template);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -223,7 +216,8 @@ public class HalFormsSerializers {
|
||||
|
||||
/**
|
||||
* Verify that the resource's self link and the affordance's URI have the same relative path.
|
||||
* @param resource
|
||||
*
|
||||
* @param resource
|
||||
* @param affordance
|
||||
* @param model
|
||||
*/
|
||||
@@ -231,15 +225,15 @@ public class HalFormsSerializers {
|
||||
|
||||
try {
|
||||
Optional<Link> selfLink = resource.getLink(Link.REL_SELF);
|
||||
|
||||
URI selfLinkUri = new URI(selfLink.map(link -> link.expand().getHref()).orElse(""));
|
||||
|
||||
if (!selfLinkUri.getPath().equals(model.getPath())) {
|
||||
throw new IllegalStateException("Affordance's URI " + model.getPath() + " doesn't match self link " + selfLinkUri.getPath() + " as expected in HAL-FORMS");
|
||||
if (!model.hasPath(selfLinkUri.getPath())) {
|
||||
throw new IllegalStateException("Affordance's URI " + model.getPath() + " doesn't match self link "
|
||||
+ selfLinkUri.getPath() + " as expected in HAL-FORMS");
|
||||
}
|
||||
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,19 +18,25 @@ package org.springframework.hateoas.hal.forms;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Wither;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.hateoas.hal.forms.HalFormsDeserializers.MediaTypesDeserializer;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
@@ -43,50 +49,79 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
* @see https://rwcbook.github.io/hal-forms/#_the_code__templates_code_element
|
||||
*/
|
||||
@Data
|
||||
@Setter(AccessLevel.NONE)
|
||||
@Wither
|
||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
@JsonAutoDetect(getterVisibility = Visibility.NON_PRIVATE)
|
||||
@JsonIgnoreProperties({ "httpMethod", "contentTypes" })
|
||||
@JsonPropertyOrder({ "title", "method", "contentType", "properties" })
|
||||
@JsonIgnoreProperties({ "key" })
|
||||
public class HalFormsTemplate {
|
||||
|
||||
public static final String DEFAULT_KEY = "default";
|
||||
|
||||
private @JsonIgnore String key;
|
||||
private List<HalFormsProperty> properties = new ArrayList<HalFormsProperty>();
|
||||
|
||||
private String title;
|
||||
private @JsonIgnore HttpMethod httpMethod;
|
||||
private List<MediaType> contentType;
|
||||
private @Wither(AccessLevel.PRIVATE) HttpMethod httpMethod;
|
||||
private List<HalFormsProperty> properties;
|
||||
private List<MediaType> contentTypes;
|
||||
|
||||
/**
|
||||
* Configure a HAL-FORMS template with a key value.
|
||||
* @param key
|
||||
*/
|
||||
public HalFormsTemplate(String key) {
|
||||
this.key = key;
|
||||
private HalFormsTemplate() {
|
||||
this(null, null, Collections.<HalFormsProperty> emptyList(), Collections.<MediaType> emptyList());
|
||||
}
|
||||
|
||||
public static HalFormsTemplate forMethod(HttpMethod httpMethod) {
|
||||
return new HalFormsTemplate().withHttpMethod(httpMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* A HAL-FORMS template with no name is dubbed the <a href="https://rwcbook.github.io/hal-forms/#_the_code__templates_code_element">"default" template</a>.
|
||||
* Returns a new {@link HalFormsTemplate} with the given {@link HalFormsProperty} added.
|
||||
*
|
||||
* @param property must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public HalFormsTemplate() {
|
||||
this(HalFormsTemplate.DEFAULT_KEY);
|
||||
public HalFormsTemplate andProperty(HalFormsProperty property) {
|
||||
|
||||
Assert.notNull(property, "Property must not be null!");
|
||||
|
||||
ArrayList<HalFormsProperty> properties = new ArrayList<HalFormsProperty>(this.properties);
|
||||
properties.add(property);
|
||||
|
||||
return new HalFormsTemplate(title, httpMethod, properties, contentTypes);
|
||||
}
|
||||
|
||||
public String getContentType() {
|
||||
return StringUtils.collectionToCommaDelimitedString(contentType);
|
||||
/**
|
||||
* Returns a new {@link HalFormsTemplate} with the given {@link MediaType} added as content type.
|
||||
*
|
||||
* @param mediaType must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public HalFormsTemplate andContentType(MediaType mediaType) {
|
||||
|
||||
Assert.notNull(mediaType, "Media type must not be null!");
|
||||
|
||||
ArrayList<MediaType> contentTypes = new ArrayList<MediaType>(this.contentTypes);
|
||||
contentTypes.add(mediaType);
|
||||
|
||||
return new HalFormsTemplate(title, httpMethod, properties, contentTypes);
|
||||
}
|
||||
|
||||
// Jackson helper methods to create the right representation format
|
||||
|
||||
String getContentType() {
|
||||
return StringUtils.collectionToDelimitedString(contentTypes, ", ");
|
||||
}
|
||||
|
||||
@JsonDeserialize(using = MediaTypesDeserializer.class)
|
||||
public void setContentType(List<MediaType> contentType) {
|
||||
this.contentType = contentType;
|
||||
void setContentType(List<MediaType> mediaTypes) {
|
||||
this.contentTypes = mediaTypes;
|
||||
}
|
||||
|
||||
public String getMethod() {
|
||||
String getMethod() {
|
||||
return this.httpMethod == null ? null : this.httpMethod.toString().toLowerCase();
|
||||
}
|
||||
|
||||
public void setMethod(String method) {
|
||||
void setMethod(String method) {
|
||||
this.httpMethod = HttpMethod.valueOf(method.toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,12 @@
|
||||
*/
|
||||
package org.springframework.hateoas.hal.forms;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
|
||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||
import org.springframework.context.support.MessageSourceAccessor;
|
||||
import org.springframework.hateoas.Link;
|
||||
@@ -32,25 +35,36 @@ import org.springframework.hateoas.hal.Jackson2HalModule.HalHandlerInstantiator;
|
||||
import org.springframework.hateoas.hal.Jackson2HalModule.HalLinkListSerializer;
|
||||
import org.springframework.hateoas.hal.LinkMixin;
|
||||
import org.springframework.hateoas.hal.ResourceSupportMixin;
|
||||
import org.springframework.hateoas.hal.forms.HalFormsDeserializers.HalFormsResourcesDeserializer;
|
||||
import org.springframework.hateoas.hal.forms.HalFormsSerializers.HalFormsResourceSerializer;
|
||||
import org.springframework.hateoas.hal.forms.HalFormsSerializers.HalFormsResourcesSerializer;
|
||||
import org.springframework.hateoas.mvc.JacksonSerializers.MediaTypeDeserializer;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.Version;
|
||||
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.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
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.module.SimpleModule;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
|
||||
/**
|
||||
* Serialize/Deserialize all the parts of HAL-FORMS documents using Jackson.
|
||||
* Serialize / deserialize all the parts of HAL-FORMS documents using Jackson.
|
||||
*
|
||||
* @author Dietrich Schulten
|
||||
* @author Greg Turnquist
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class Jackson2HalFormsModule extends SimpleModule {
|
||||
|
||||
@@ -65,8 +79,37 @@ public class Jackson2HalFormsModule extends SimpleModule {
|
||||
setMixInAnnotation(Resource.class, ResourceMixin.class);
|
||||
setMixInAnnotation(Resources.class, ResourcesMixin.class);
|
||||
setMixInAnnotation(PagedResources.class, PagedResourcesMixin.class);
|
||||
setMixInAnnotation(MediaType.class, MediaTypeMixin.class);
|
||||
}
|
||||
|
||||
@JsonSerialize(using = HalFormsResourceSerializer.class)
|
||||
static interface ResourceMixin {}
|
||||
|
||||
@JsonSerialize(using = HalFormsResourcesSerializer.class)
|
||||
abstract class ResourcesMixin<T> extends Resources<T> {
|
||||
|
||||
@Override
|
||||
@XmlElement(name = "embedded")
|
||||
@JsonProperty("_embedded")
|
||||
@JsonInclude(Include.NON_EMPTY)
|
||||
@JsonDeserialize(using = HalFormsResourcesDeserializer.class)
|
||||
public abstract Collection<T> getContent();
|
||||
}
|
||||
|
||||
abstract class PagedResourcesMixin<T> extends PagedResources<T> {
|
||||
|
||||
@Override
|
||||
@JsonProperty("page")
|
||||
@JsonInclude(Include.NON_EMPTY)
|
||||
public PageMetadata getMetadata() {
|
||||
return super.getMetadata();
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@JsonDeserialize(using = MediaTypeDeserializer.class)
|
||||
static interface MediaTypeMixin {}
|
||||
|
||||
/**
|
||||
* Create new HAL-FORMS serializers based on the context.
|
||||
*/
|
||||
@@ -99,41 +142,33 @@ public class Jackson2HalFormsModule extends SimpleModule {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* com.fasterxml.jackson.databind.cfg.HandlerInstantiator#deserializerInstance(com.fasterxml.jackson.databind.
|
||||
* DeserializationConfig, com.fasterxml.jackson.databind.introspect.Annotated, java.lang.Class)
|
||||
* @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) {
|
||||
Class<?> deserClass) {
|
||||
|
||||
Object jsonDeser = findInstance(deserClass);
|
||||
return jsonDeser != null ? (JsonDeserializer<?>) jsonDeser
|
||||
: super.deserializerInstance(config, annotated, deserClass);
|
||||
: super.deserializerInstance(config, annotated, 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)
|
||||
* @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) {
|
||||
Class<?> keyDeserClass) {
|
||||
|
||||
Object keyDeser = findInstance(keyDeserClass);
|
||||
return keyDeser != null ? (KeyDeserializer) keyDeser
|
||||
: super.keyDeserializerInstance(config, annotated, keyDeserClass);
|
||||
: super.keyDeserializerInstance(config, annotated, 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)
|
||||
* @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) {
|
||||
@@ -144,33 +179,27 @@ public class Jackson2HalFormsModule extends SimpleModule {
|
||||
|
||||
/*
|
||||
* (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)
|
||||
* @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) {
|
||||
Class<?> builderClass) {
|
||||
|
||||
Object builder = findInstance(builderClass);
|
||||
return builder != null ? (TypeResolverBuilder<?>) builder
|
||||
: super.typeResolverBuilderInstance(config, annotated, builderClass);
|
||||
: super.typeResolverBuilderInstance(config, annotated, 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)
|
||||
* @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);
|
||||
: super.typeIdResolverInstance(config, annotated, resolverClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 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
|
||||
*
|
||||
* http://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.hal.forms;
|
||||
|
||||
import org.springframework.hateoas.PagedResources;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Custom mixin to render {@link org.springframework.hateoas.PagedResources.PageMetadata} in HAL.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
abstract class PagedResourcesMixin<T> extends PagedResources<T> {
|
||||
|
||||
@Override
|
||||
@JsonProperty("page")
|
||||
@JsonInclude(Include.NON_EMPTY)
|
||||
public PageMetadata getMetadata() {
|
||||
return super.getMetadata();
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 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
|
||||
*
|
||||
* http://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.hal.forms;
|
||||
|
||||
import org.springframework.hateoas.hal.forms.HalFormsSerializers.HalFormsResourceSerializer;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
|
||||
/**
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@JsonSerialize(using = HalFormsResourceSerializer.class)
|
||||
abstract class ResourceMixin {
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 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
|
||||
*
|
||||
* http://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.hal.forms;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
|
||||
import org.springframework.hateoas.Resources;
|
||||
import org.springframework.hateoas.hal.forms.HalFormsDeserializers.HalFormsResourcesDeserializer;
|
||||
import org.springframework.hateoas.hal.forms.HalFormsSerializers.HalFormsResourcesSerializer;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
|
||||
/**
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@JsonSerialize(using = HalFormsResourcesSerializer.class)
|
||||
abstract class ResourcesMixin<T> extends Resources<T> {
|
||||
|
||||
@Override
|
||||
@XmlElement(name = "embedded")
|
||||
@JsonProperty("_embedded")
|
||||
@JsonInclude(Include.NON_EMPTY)
|
||||
@JsonDeserialize(using = HalFormsResourcesDeserializer.class)
|
||||
public abstract Collection<T> getContent();
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user