#1442 - Exposes consumed media types on affordances.
InputPayloadMetadata now captures and exposes supported media types. SpringAffordanceBuilder obtains them from the newly introduced API available on MappingDiscoverer.
This commit is contained in:
@@ -257,16 +257,29 @@ public abstract class AffordanceModel {
|
||||
* @return will never be {@literal null}.
|
||||
* @since 1.3
|
||||
*/
|
||||
InputPayloadMetadata withMediaType(@Nullable MediaType mediaType);
|
||||
InputPayloadMetadata withMediaTypes(List<MediaType> mediaType);
|
||||
|
||||
/**
|
||||
* Returns the {@link MediaType} that the payload requires.
|
||||
*
|
||||
* @return can be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
* @since 1.3
|
||||
*/
|
||||
List<MediaType> getMediaTypes();
|
||||
|
||||
/**
|
||||
* Returns the primary {@link MediaType} expected for the input. That is, from {@link #getMediaTypes()} the first
|
||||
* one, if available.
|
||||
*
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
MediaType getMediaType();
|
||||
default MediaType getPrimaryMediaType() {
|
||||
|
||||
List<MediaType> mediaTypes = getMediaTypes();
|
||||
|
||||
return mediaTypes.isEmpty() ? null : mediaTypes.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,15 +290,15 @@ public abstract class AffordanceModel {
|
||||
private static class DelegatingInputPayloadMetadata implements InputPayloadMetadata {
|
||||
|
||||
private final PayloadMetadata metadata;
|
||||
private final MediaType mediaType;
|
||||
private final List<MediaType> mediaTypes;
|
||||
|
||||
public static DelegatingInputPayloadMetadata of(PayloadMetadata metadata) {
|
||||
return new DelegatingInputPayloadMetadata(metadata, null);
|
||||
return new DelegatingInputPayloadMetadata(metadata, Collections.emptyList());
|
||||
}
|
||||
|
||||
private DelegatingInputPayloadMetadata(PayloadMetadata metadata, MediaType mediaType) {
|
||||
private DelegatingInputPayloadMetadata(PayloadMetadata metadata, List<MediaType> mediaTypes) {
|
||||
this.metadata = metadata;
|
||||
this.mediaType = mediaType;
|
||||
this.mediaTypes = mediaTypes;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -326,20 +339,20 @@ public abstract class AffordanceModel {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.AffordanceModel.InputPayloadMetadata#withMediaType(org.springframework.http.MediaType)
|
||||
* @see org.springframework.hateoas.AffordanceModel.InputPayloadMetadata#withMediaTypes(java.util.List)
|
||||
*/
|
||||
@Override
|
||||
public InputPayloadMetadata withMediaType(MediaType mediaType) {
|
||||
return new DelegatingInputPayloadMetadata(metadata, mediaType);
|
||||
public InputPayloadMetadata withMediaTypes(List<MediaType> mediaTypes) {
|
||||
return new DelegatingInputPayloadMetadata(metadata, mediaTypes);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.AffordanceModel.InputPayloadMetadata#getMediaType()
|
||||
* @see org.springframework.hateoas.AffordanceModel.InputPayloadMetadata#getMediaTypes()
|
||||
*/
|
||||
@Override
|
||||
public MediaType getMediaType() {
|
||||
return mediaType;
|
||||
public List<MediaType> getMediaTypes() {
|
||||
return mediaTypes;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -215,8 +215,17 @@ public class Affordances implements AffordanceOperations {
|
||||
* @see org.springframework.hateoas.mediatype.ConfigurableAffordance#withInputMediaType(org.springframework.http.MediaType)
|
||||
*/
|
||||
@Override
|
||||
public AffordanceBuilder withInputMediaType(@Nullable MediaType inputMediaType) {
|
||||
return withInput(inputMetdata.withMediaType(inputMediaType));
|
||||
public ConfigurableAffordance withInputMediaType(MediaType inputMediaType) {
|
||||
return withInputMediaTypes(Arrays.asList(inputMediaType));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.mediatype.ConfigurableAffordance#withInputMediaTypes(java.util.List)
|
||||
*/
|
||||
@Override
|
||||
public ConfigurableAffordance withInputMediaTypes(List<MediaType> inputMediaTypes) {
|
||||
return withInput(inputMetdata.withMediaTypes(inputMediaTypes));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -111,10 +111,18 @@ public interface ConfigurableAffordance extends AffordanceOperations {
|
||||
/**
|
||||
* Registers the input to expect to be of the given {@link MediaType}.
|
||||
*
|
||||
* @param inputMediaType
|
||||
* @return
|
||||
* @param inputMediaType can be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
ConfigurableAffordance withInputMediaType(@Nullable MediaType inputMediaType);
|
||||
ConfigurableAffordance withInputMediaType(MediaType inputMediaType);
|
||||
|
||||
/**
|
||||
* Registers the given {@link MediaType}s as input payload media types.
|
||||
*
|
||||
* @param inputMediaTypes must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
ConfigurableAffordance withInputMediaTypes(List<MediaType> inputMediaTypes);
|
||||
|
||||
/**
|
||||
* Replaces the current {@link QueryParameter} list with the given ones.
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.hateoas.mediatype;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
@@ -28,7 +29,7 @@ import org.springframework.hateoas.AffordanceModel.InputPayloadMetadata;
|
||||
import org.springframework.hateoas.AffordanceModel.Named;
|
||||
import org.springframework.hateoas.AffordanceModel.PropertyMetadata;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link InputPayloadMetadata} implementation based on a Java type.
|
||||
@@ -39,19 +40,23 @@ class TypeBasedPayloadMetadata implements InputPayloadMetadata {
|
||||
|
||||
private final ResolvableType type;
|
||||
private final SortedMap<String, PropertyMetadata> properties;
|
||||
private final @Nullable MediaType mediaType;
|
||||
private final List<MediaType> mediaTypes;
|
||||
|
||||
TypeBasedPayloadMetadata(ResolvableType type, Stream<PropertyMetadata> properties) {
|
||||
this(type, new TreeMap<>(
|
||||
properties.collect(Collectors.toMap(PropertyMetadata::getName, Function.identity()))), null);
|
||||
properties.collect(Collectors.toMap(PropertyMetadata::getName, Function.identity()))), Collections.emptyList());
|
||||
}
|
||||
|
||||
TypeBasedPayloadMetadata(ResolvableType type, SortedMap<String, PropertyMetadata> properties,
|
||||
@Nullable MediaType mediaType) {
|
||||
List<MediaType> mediaTypes) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
Assert.notNull(properties, "Properties must not be null!");
|
||||
Assert.notNull(mediaTypes, "Media types must not be null!");
|
||||
|
||||
this.type = type;
|
||||
this.properties = properties;
|
||||
this.mediaType = mediaType;
|
||||
this.mediaTypes = mediaTypes;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -93,20 +98,19 @@ class TypeBasedPayloadMetadata implements InputPayloadMetadata {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.AffordanceModel.InputPayloadMetadata#withMediaType(org.springframework.http.MediaType)
|
||||
* @see org.springframework.hateoas.AffordanceModel.InputPayloadMetadata#withMediaTypes(java.util.List)
|
||||
*/
|
||||
@Override
|
||||
public InputPayloadMetadata withMediaType(@Nullable MediaType mediaType) {
|
||||
return new TypeBasedPayloadMetadata(type, properties, mediaType);
|
||||
public InputPayloadMetadata withMediaTypes(List<MediaType> mediaTypes) {
|
||||
return new TypeBasedPayloadMetadata(type, properties, mediaTypes);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.AffordanceModel.InputPayloadMetadata#getMediaType()
|
||||
* @see org.springframework.hateoas.AffordanceModel.InputPayloadMetadata#getMediaTypes()
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public MediaType getMediaType() {
|
||||
return mediaType;
|
||||
public List<MediaType> getMediaTypes() {
|
||||
return mediaTypes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,13 +21,16 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
|
||||
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.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
@@ -153,6 +156,21 @@ public class AnnotationMappingDiscoverer implements MappingDiscoverer {
|
||||
return requestMethodNames;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.server.core.MappingDiscoverer#getConsumes(java.lang.reflect.Method)
|
||||
*/
|
||||
@Override
|
||||
public List<MediaType> getConsumes(Method method) {
|
||||
|
||||
Annotation annotation = findMergedAnnotation(method, annotationType);
|
||||
String[] mediaTypes = (String[]) getValue(annotation, "consumes");
|
||||
|
||||
return mediaTypes == null
|
||||
? Collections.emptyList()
|
||||
: Arrays.stream(mediaTypes).map(MediaType::parseMediaType).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private String[] getMappingFrom(@Nullable Annotation annotation) {
|
||||
|
||||
if (annotation == null) {
|
||||
|
||||
@@ -17,9 +17,11 @@ package org.springframework.hateoas.server.core;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -93,6 +95,15 @@ public class CachingMappingDiscoverer implements MappingDiscoverer {
|
||||
return METHODS.computeIfAbsent(key(type, method), __ -> delegate.getRequestMethod(type, method));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.server.core.MappingDiscoverer#getConsumes(java.lang.reflect.Method)
|
||||
*/
|
||||
@Override
|
||||
public List<MediaType> getConsumes(Method method) {
|
||||
return delegate.getConsumes(method);
|
||||
}
|
||||
|
||||
private static String key(Class<?> type, @Nullable Method method) {
|
||||
|
||||
StringBuilder builder = new StringBuilder(type.getName());
|
||||
|
||||
@@ -17,8 +17,10 @@ package org.springframework.hateoas.server.core;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -68,4 +70,13 @@ public interface MappingDiscoverer {
|
||||
* @return
|
||||
*/
|
||||
Collection<HttpMethod> getRequestMethod(Class<?> type, Method method);
|
||||
|
||||
/**
|
||||
* Returns the {@link MediaType}s supported for consumption.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @return can be {@literal null}.
|
||||
* @since 1.3
|
||||
*/
|
||||
List<MediaType> getConsumes(Method method);
|
||||
}
|
||||
|
||||
@@ -17,8 +17,10 @@ package org.springframework.hateoas.server.core;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.context.ContextLoader;
|
||||
@@ -80,6 +82,15 @@ class PropertyResolvingMappingDiscoverer implements MappingDiscoverer {
|
||||
return delegate.getRequestMethod(type, method);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.server.core.MappingDiscoverer#getConsumes(java.lang.reflect.Method)
|
||||
*/
|
||||
@Override
|
||||
public List<MediaType> getConsumes(Method method) {
|
||||
return delegate.getConsumes(method);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String resolveProperties(@Nullable String mapping) {
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ public class SpringAffordanceBuilder {
|
||||
.withOutput(outputType) //
|
||||
.withParameters(queryMethodParameters) //
|
||||
.withName(methodName) //
|
||||
.withInputMediaTypes(discoverer.getConsumes(method)) //
|
||||
.build() //
|
||||
.stream()) //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
@@ -20,6 +20,7 @@ import static org.assertj.core.api.Assertions.*;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@@ -160,6 +161,16 @@ class AnnotationMappingDiscovererUnitTest {
|
||||
assertThat(discoverer.getMapping(method)).isEqualTo("/type/foo/{bar}");
|
||||
}
|
||||
|
||||
@Test // #1442
|
||||
void exposesConsumesClause() throws Exception {
|
||||
|
||||
Method method = MyController.class.getMethod("mappingWithConsumesClause");
|
||||
assertThat(discoverer.getConsumes(method)).containsExactly(MediaType.APPLICATION_JSON);
|
||||
|
||||
method = MyController.class.getMethod("method");
|
||||
assertThat(discoverer.getConsumes(method)).isEmpty();
|
||||
}
|
||||
|
||||
@RequestMapping("/type")
|
||||
interface MyController {
|
||||
|
||||
@@ -174,6 +185,9 @@ class AnnotationMappingDiscovererUnitTest {
|
||||
|
||||
@RequestMapping("/foo/{bar:[ABC]{1}}")
|
||||
void mappingWithMatchingExpression();
|
||||
|
||||
@RequestMapping(path = "/path", consumes = "application/json")
|
||||
void mappingWithConsumesClause();
|
||||
}
|
||||
|
||||
interface ControllerWithoutTypeLevelMapping {
|
||||
|
||||
Reference in New Issue
Block a user