Allow an operation to specify the media types that it produces
Closes gh-10118
This commit is contained in:
parent
80f023f996
commit
f49741e3ed
|
|
@ -26,6 +26,7 @@ import java.lang.annotation.Target;
|
||||||
* Identifies a method on an {@link Endpoint} as being a delete operation.
|
* Identifies a method on an {@link Endpoint} as being a delete operation.
|
||||||
*
|
*
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
|
* @author Andy Wilkinson
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
@Target(ElementType.METHOD)
|
@Target(ElementType.METHOD)
|
||||||
|
|
@ -33,4 +34,11 @@ import java.lang.annotation.Target;
|
||||||
@Documented
|
@Documented
|
||||||
public @interface DeleteOperation {
|
public @interface DeleteOperation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The media type of the result of the operation.
|
||||||
|
*
|
||||||
|
* @return the media type
|
||||||
|
*/
|
||||||
|
String[] produces() default {};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,4 +33,11 @@ import java.lang.annotation.Target;
|
||||||
@Documented
|
@Documented
|
||||||
public @interface ReadOperation {
|
public @interface ReadOperation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The media type of the result of the operation.
|
||||||
|
*
|
||||||
|
* @return the media type
|
||||||
|
*/
|
||||||
|
String[] produces() default {};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,4 +33,11 @@ import java.lang.annotation.Target;
|
||||||
@Documented
|
@Documented
|
||||||
public @interface WriteOperation {
|
public @interface WriteOperation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The media type of the result of the operation.
|
||||||
|
*
|
||||||
|
* @return the media type
|
||||||
|
*/
|
||||||
|
String[] produces() default {};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package org.springframework.boot.endpoint.web;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -134,7 +135,8 @@ public class WebAnnotationEndpointDiscoverer extends
|
||||||
OperationRequestPredicate requestPredicate = new OperationRequestPredicate(
|
OperationRequestPredicate requestPredicate = new OperationRequestPredicate(
|
||||||
determinePath(endpointId, method), httpMethod,
|
determinePath(endpointId, method), httpMethod,
|
||||||
determineConsumedMediaTypes(httpMethod, method),
|
determineConsumedMediaTypes(httpMethod, method),
|
||||||
determineProducedMediaTypes(method));
|
determineProducedMediaTypes(
|
||||||
|
operationAttributes.getStringArray("produces"), method));
|
||||||
OperationInvoker invoker = new ReflectiveOperationInvoker(
|
OperationInvoker invoker = new ReflectiveOperationInvoker(
|
||||||
this.parameterMapper, target, method);
|
this.parameterMapper, target, method);
|
||||||
if (timeToLive > 0) {
|
if (timeToLive > 0) {
|
||||||
|
|
@ -171,7 +173,11 @@ public class WebAnnotationEndpointDiscoverer extends
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Collection<String> determineProducedMediaTypes(Method method) {
|
private Collection<String> determineProducedMediaTypes(String[] produces,
|
||||||
|
Method method) {
|
||||||
|
if (produces.length > 0) {
|
||||||
|
return Arrays.asList(produces);
|
||||||
|
}
|
||||||
if (Void.class.equals(method.getReturnType())
|
if (Void.class.equals(method.getReturnType())
|
||||||
|| void.class.equals(method.getReturnType())) {
|
|| void.class.equals(method.getReturnType())) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
|
|
|
||||||
|
|
@ -254,6 +254,14 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
||||||
.isEqualTo("alpha"));
|
.isEqualTo("alpha"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readOperationWithCustomMediaType() {
|
||||||
|
load(CustomMediaTypesEndpointConfiguration.class,
|
||||||
|
(client) -> client.get().uri("/custommediatypes").exchange()
|
||||||
|
.expectStatus().isOk().expectHeader()
|
||||||
|
.valueMatches("Content-Type", "text/plain(;charset=.*)?"));
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract T createApplicationContext(Class<?>... config);
|
protected abstract T createApplicationContext(Class<?>... config);
|
||||||
|
|
||||||
protected abstract int getPort(T context);
|
protected abstract int getPort(T context);
|
||||||
|
|
@ -422,6 +430,17 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Import(BaseConfiguration.class)
|
||||||
|
static class CustomMediaTypesEndpointConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CustomMediaTypesEndpoint customMediaTypesEndpoint() {
|
||||||
|
return new CustomMediaTypesEndpoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Endpoint(id = "test")
|
@Endpoint(id = "test")
|
||||||
static class TestEndpoint {
|
static class TestEndpoint {
|
||||||
|
|
||||||
|
|
@ -580,6 +599,16 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Endpoint(id = "custommediatypes")
|
||||||
|
static class CustomMediaTypesEndpoint {
|
||||||
|
|
||||||
|
@ReadOperation(produces = "text/plain")
|
||||||
|
public String read() {
|
||||||
|
return "read";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public interface EndpointDelegate {
|
public interface EndpointDelegate {
|
||||||
|
|
||||||
void write();
|
void write();
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import org.junit.rules.ExpectedException;
|
||||||
import org.springframework.boot.endpoint.CachingConfiguration;
|
import org.springframework.boot.endpoint.CachingConfiguration;
|
||||||
import org.springframework.boot.endpoint.CachingOperationInvoker;
|
import org.springframework.boot.endpoint.CachingOperationInvoker;
|
||||||
import org.springframework.boot.endpoint.ConversionServiceOperationParameterMapper;
|
import org.springframework.boot.endpoint.ConversionServiceOperationParameterMapper;
|
||||||
|
import org.springframework.boot.endpoint.DeleteOperation;
|
||||||
import org.springframework.boot.endpoint.Endpoint;
|
import org.springframework.boot.endpoint.Endpoint;
|
||||||
import org.springframework.boot.endpoint.EndpointExposure;
|
import org.springframework.boot.endpoint.EndpointExposure;
|
||||||
import org.springframework.boot.endpoint.EndpointInfo;
|
import org.springframework.boot.endpoint.EndpointInfo;
|
||||||
|
|
@ -216,6 +217,24 @@ public class WebAnnotationEndpointDiscovererTests {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void operationCanProduceCustomMediaTypes() {
|
||||||
|
load(CustomMediaTypesEndpointConfiguration.class, (discoverer) -> {
|
||||||
|
Map<String, EndpointInfo<WebEndpointOperation>> endpoints = mapEndpoints(
|
||||||
|
discoverer.discoverEndpoints());
|
||||||
|
assertThat(endpoints).containsOnlyKeys("custommediatypes");
|
||||||
|
EndpointInfo<WebEndpointOperation> endpoint = endpoints
|
||||||
|
.get("custommediatypes");
|
||||||
|
assertThat(requestPredicates(endpoint)).has(requestPredicates(
|
||||||
|
path("custommediatypes").httpMethod(WebEndpointHttpMethod.GET)
|
||||||
|
.consumes().produces("text/plain"),
|
||||||
|
path("custommediatypes").httpMethod(WebEndpointHttpMethod.POST)
|
||||||
|
.consumes().produces("a/b", "c/d"),
|
||||||
|
path("custommediatypes").httpMethod(WebEndpointHttpMethod.DELETE)
|
||||||
|
.consumes().produces("text/plain")));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void load(Class<?> configuration,
|
private void load(Class<?> configuration,
|
||||||
Consumer<WebAnnotationEndpointDiscoverer> consumer) {
|
Consumer<WebAnnotationEndpointDiscoverer> consumer) {
|
||||||
this.load((id) -> null, configuration, consumer);
|
this.load((id) -> null, configuration, consumer);
|
||||||
|
|
@ -415,6 +434,27 @@ public class WebAnnotationEndpointDiscovererTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Endpoint(id = "custommediatypes")
|
||||||
|
static class CustomMediaTypesEndpoint {
|
||||||
|
|
||||||
|
@ReadOperation(produces = "text/plain")
|
||||||
|
public String read() {
|
||||||
|
return "read";
|
||||||
|
}
|
||||||
|
|
||||||
|
@WriteOperation(produces = { "a/b", "c/d" })
|
||||||
|
public String write() {
|
||||||
|
return "write";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteOperation(produces = "text/plain")
|
||||||
|
public String delete() {
|
||||||
|
return "delete";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
static class MultipleEndpointsConfiguration {
|
static class MultipleEndpointsConfiguration {
|
||||||
|
|
||||||
|
|
@ -578,6 +618,17 @@ public class WebAnnotationEndpointDiscovererTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Import(BaseConfiguration.class)
|
||||||
|
static class CustomMediaTypesEndpointConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CustomMediaTypesEndpoint customMediaTypesEndpoint() {
|
||||||
|
return new CustomMediaTypesEndpoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private static final class RequestPredicateMatcher {
|
private static final class RequestPredicateMatcher {
|
||||||
|
|
||||||
private final String path;
|
private final String path;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue