Allow an operation to specify the media types that it produces

Closes gh-10118
pull/10130/head
Andy Wilkinson 7 years ago
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.
*
* @author Stephane Nicoll
* @author Andy Wilkinson
* @since 2.0.0
*/
@Target(ElementType.METHOD)
@ -33,4 +34,11 @@ import java.lang.annotation.Target;
@Documented
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
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
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.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@ -134,7 +135,8 @@ public class WebAnnotationEndpointDiscoverer extends
OperationRequestPredicate requestPredicate = new OperationRequestPredicate(
determinePath(endpointId, method), httpMethod,
determineConsumedMediaTypes(httpMethod, method),
determineProducedMediaTypes(method));
determineProducedMediaTypes(
operationAttributes.getStringArray("produces"), method));
OperationInvoker invoker = new ReflectiveOperationInvoker(
this.parameterMapper, target, method);
if (timeToLive > 0) {
@ -171,7 +173,11 @@ public class WebAnnotationEndpointDiscoverer extends
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())
|| void.class.equals(method.getReturnType())) {
return Collections.emptyList();

@ -254,6 +254,14 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
.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 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")
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 {
void write();

@ -36,6 +36,7 @@ import org.junit.rules.ExpectedException;
import org.springframework.boot.endpoint.CachingConfiguration;
import org.springframework.boot.endpoint.CachingOperationInvoker;
import org.springframework.boot.endpoint.ConversionServiceOperationParameterMapper;
import org.springframework.boot.endpoint.DeleteOperation;
import org.springframework.boot.endpoint.Endpoint;
import org.springframework.boot.endpoint.EndpointExposure;
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,
Consumer<WebAnnotationEndpointDiscoverer> 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
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 final String path;

Loading…
Cancel
Save