Skip to content

Commit f49741e

Browse files
committed
Allow an operation to specify the media types that it produces
Closes gh-10118
1 parent 80f023f commit f49741e

File tree

6 files changed

+110
-2
lines changed

6 files changed

+110
-2
lines changed

spring-boot/src/main/java/org/springframework/boot/endpoint/DeleteOperation.java

+8
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,19 @@
2626
* Identifies a method on an {@link Endpoint} as being a delete operation.
2727
*
2828
* @author Stephane Nicoll
29+
* @author Andy Wilkinson
2930
* @since 2.0.0
3031
*/
3132
@Target(ElementType.METHOD)
3233
@Retention(RetentionPolicy.RUNTIME)
3334
@Documented
3435
public @interface DeleteOperation {
3536

37+
/**
38+
* The media type of the result of the operation.
39+
*
40+
* @return the media type
41+
*/
42+
String[] produces() default {};
43+
3644
}

spring-boot/src/main/java/org/springframework/boot/endpoint/ReadOperation.java

+7
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,11 @@
3333
@Documented
3434
public @interface ReadOperation {
3535

36+
/**
37+
* The media type of the result of the operation.
38+
*
39+
* @return the media type
40+
*/
41+
String[] produces() default {};
42+
3643
}

spring-boot/src/main/java/org/springframework/boot/endpoint/WriteOperation.java

+7
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,11 @@
3333
@Documented
3434
public @interface WriteOperation {
3535

36+
/**
37+
* The media type of the result of the operation.
38+
*
39+
* @return the media type
40+
*/
41+
String[] produces() default {};
42+
3643
}

spring-boot/src/main/java/org/springframework/boot/endpoint/web/WebAnnotationEndpointDiscoverer.java

+8-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.lang.reflect.Method;
2020
import java.util.ArrayList;
21+
import java.util.Arrays;
2122
import java.util.Collection;
2223
import java.util.Collections;
2324
import java.util.List;
@@ -134,7 +135,8 @@ public WebEndpointOperation createOperation(String endpointId,
134135
OperationRequestPredicate requestPredicate = new OperationRequestPredicate(
135136
determinePath(endpointId, method), httpMethod,
136137
determineConsumedMediaTypes(httpMethod, method),
137-
determineProducedMediaTypes(method));
138+
determineProducedMediaTypes(
139+
operationAttributes.getStringArray("produces"), method));
138140
OperationInvoker invoker = new ReflectiveOperationInvoker(
139141
this.parameterMapper, target, method);
140142
if (timeToLive > 0) {
@@ -171,7 +173,11 @@ private Collection<String> determineConsumedMediaTypes(
171173
return Collections.emptyList();
172174
}
173175

174-
private Collection<String> determineProducedMediaTypes(Method method) {
176+
private Collection<String> determineProducedMediaTypes(String[] produces,
177+
Method method) {
178+
if (produces.length > 0) {
179+
return Arrays.asList(produces);
180+
}
175181
if (Void.class.equals(method.getReturnType())
176182
|| void.class.equals(method.getReturnType())) {
177183
return Collections.emptyList();

spring-boot/src/test/java/org/springframework/boot/endpoint/web/AbstractWebEndpointIntegrationTests.java

+29
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,14 @@ public void readOperationWithMonoResponse() {
254254
.isEqualTo("alpha"));
255255
}
256256

257+
@Test
258+
public void readOperationWithCustomMediaType() {
259+
load(CustomMediaTypesEndpointConfiguration.class,
260+
(client) -> client.get().uri("/custommediatypes").exchange()
261+
.expectStatus().isOk().expectHeader()
262+
.valueMatches("Content-Type", "text/plain(;charset=.*)?"));
263+
}
264+
257265
protected abstract T createApplicationContext(Class<?>... config);
258266

259267
protected abstract int getPort(T context);
@@ -422,6 +430,17 @@ public MonoResponseEndpoint testEndpoint(EndpointDelegate endpointDelegate) {
422430

423431
}
424432

433+
@Configuration
434+
@Import(BaseConfiguration.class)
435+
static class CustomMediaTypesEndpointConfiguration {
436+
437+
@Bean
438+
public CustomMediaTypesEndpoint customMediaTypesEndpoint() {
439+
return new CustomMediaTypesEndpoint();
440+
}
441+
442+
}
443+
425444
@Endpoint(id = "test")
426445
static class TestEndpoint {
427446

@@ -580,6 +599,16 @@ Mono<Map<String, String>> operation() {
580599

581600
}
582601

602+
@Endpoint(id = "custommediatypes")
603+
static class CustomMediaTypesEndpoint {
604+
605+
@ReadOperation(produces = "text/plain")
606+
public String read() {
607+
return "read";
608+
}
609+
610+
}
611+
583612
public interface EndpointDelegate {
584613

585614
void write();

spring-boot/src/test/java/org/springframework/boot/endpoint/web/WebAnnotationEndpointDiscovererTests.java

+51
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.springframework.boot.endpoint.CachingConfiguration;
3737
import org.springframework.boot.endpoint.CachingOperationInvoker;
3838
import org.springframework.boot.endpoint.ConversionServiceOperationParameterMapper;
39+
import org.springframework.boot.endpoint.DeleteOperation;
3940
import org.springframework.boot.endpoint.Endpoint;
4041
import org.springframework.boot.endpoint.EndpointExposure;
4142
import org.springframework.boot.endpoint.EndpointInfo;
@@ -216,6 +217,24 @@ public void operationsThatReturnResourceProduceApplicationOctetStream() {
216217
});
217218
}
218219

220+
@Test
221+
public void operationCanProduceCustomMediaTypes() {
222+
load(CustomMediaTypesEndpointConfiguration.class, (discoverer) -> {
223+
Map<String, EndpointInfo<WebEndpointOperation>> endpoints = mapEndpoints(
224+
discoverer.discoverEndpoints());
225+
assertThat(endpoints).containsOnlyKeys("custommediatypes");
226+
EndpointInfo<WebEndpointOperation> endpoint = endpoints
227+
.get("custommediatypes");
228+
assertThat(requestPredicates(endpoint)).has(requestPredicates(
229+
path("custommediatypes").httpMethod(WebEndpointHttpMethod.GET)
230+
.consumes().produces("text/plain"),
231+
path("custommediatypes").httpMethod(WebEndpointHttpMethod.POST)
232+
.consumes().produces("a/b", "c/d"),
233+
path("custommediatypes").httpMethod(WebEndpointHttpMethod.DELETE)
234+
.consumes().produces("text/plain")));
235+
});
236+
}
237+
219238
private void load(Class<?> configuration,
220239
Consumer<WebAnnotationEndpointDiscoverer> consumer) {
221240
this.load((id) -> null, configuration, consumer);
@@ -415,6 +434,27 @@ public Resource read() {
415434

416435
}
417436

437+
@Endpoint(id = "custommediatypes")
438+
static class CustomMediaTypesEndpoint {
439+
440+
@ReadOperation(produces = "text/plain")
441+
public String read() {
442+
return "read";
443+
}
444+
445+
@WriteOperation(produces = { "a/b", "c/d" })
446+
public String write() {
447+
return "write";
448+
449+
}
450+
451+
@DeleteOperation(produces = "text/plain")
452+
public String delete() {
453+
return "delete";
454+
}
455+
456+
}
457+
418458
@Configuration
419459
static class MultipleEndpointsConfiguration {
420460

@@ -578,6 +618,17 @@ public ResourceEndpoint resourceEndpoint() {
578618

579619
}
580620

621+
@Configuration
622+
@Import(BaseConfiguration.class)
623+
static class CustomMediaTypesEndpointConfiguration {
624+
625+
@Bean
626+
public CustomMediaTypesEndpoint customMediaTypesEndpoint() {
627+
return new CustomMediaTypesEndpoint();
628+
}
629+
630+
}
631+
581632
private static final class RequestPredicateMatcher {
582633

583634
private final String path;

0 commit comments

Comments
 (0)