Skip to content

Commit 0958498

Browse files
committed
Merge branch '2.5.x' into 2.6.x
Closes gh-30161
2 parents ff1352f + 6eacc07 commit 0958498

File tree

6 files changed

+109
-6
lines changed

6 files changed

+109
-6
lines changed

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBean.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@
2929
import javax.management.MBeanInfo;
3030
import javax.management.ReflectionException;
3131

32+
import reactor.core.publisher.Flux;
3233
import reactor.core.publisher.Mono;
3334

3435
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
@@ -172,6 +173,9 @@ public AttributeList setAttributes(AttributeList attributes) {
172173
private static class ReactiveHandler {
173174

174175
static Object handle(Object result) {
176+
if (result instanceof Flux) {
177+
result = ((Flux<?>) result).collectList();
178+
}
175179
if (result instanceof Mono) {
176180
return ((Mono<?>) result).block();
177181
}

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/jersey/JerseyEndpointResourceFactory.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -38,6 +38,7 @@
3838
import org.glassfish.jersey.server.ContainerRequest;
3939
import org.glassfish.jersey.server.model.Resource;
4040
import org.glassfish.jersey.server.model.Resource.Builder;
41+
import reactor.core.publisher.Flux;
4142
import reactor.core.publisher.Mono;
4243

4344
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
@@ -138,6 +139,7 @@ private static final class OperationInflector implements Inflector<ContainerRequ
138139
List<Function<Object, Object>> converters = new ArrayList<>();
139140
converters.add(new ResourceBodyConverter());
140141
if (ClassUtils.isPresent("reactor.core.publisher.Mono", OperationInflector.class.getClassLoader())) {
142+
converters.add(new FluxBodyConverter());
141143
converters.add(new MonoBodyConverter());
142144
}
143145
BODY_CONVERTERS = Collections.unmodifiableList(converters);
@@ -297,6 +299,21 @@ public Object apply(Object body) {
297299

298300
}
299301

302+
/**
303+
* Body converter from {@link Flux} to {@link Flux#collectList Mono&lt;List&gt;}.
304+
*/
305+
private static final class FluxBodyConverter implements Function<Object, Object> {
306+
307+
@Override
308+
public Object apply(Object body) {
309+
if (body instanceof Flux) {
310+
return ((Flux<?>) body).collectList();
311+
}
312+
return body;
313+
}
314+
315+
}
316+
300317
/**
301318
* {@link Inflector} to for endpoint links.
302319
*/

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMapping.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
2626
import java.util.function.Supplier;
2727

2828
import org.reactivestreams.Publisher;
29+
import reactor.core.publisher.Flux;
2930
import reactor.core.publisher.Mono;
3031
import reactor.core.scheduler.Schedulers;
3132

@@ -362,6 +363,9 @@ private Map<String, String> getTemplateVariables(ServerWebExchange exchange) {
362363
}
363364

364365
private Mono<ResponseEntity<Object>> handleResult(Publisher<?> result, HttpMethod httpMethod) {
366+
if (result instanceof Flux) {
367+
result = ((Flux<?>) result).collectList();
368+
}
365369
return Mono.from(result).map(this::toResponseEntity)
366370
.onErrorMap(InvalidEndpointRequestException.class,
367371
(ex) -> new ResponseStatusException(HttpStatus.BAD_REQUEST, ex.getReason()))

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/AbstractWebMvcEndpointHandlerMapping.java

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,16 +19,21 @@
1919
import java.lang.reflect.Method;
2020
import java.nio.charset.StandardCharsets;
2121
import java.security.Principal;
22+
import java.util.ArrayList;
2223
import java.util.Arrays;
2324
import java.util.Collection;
25+
import java.util.Collections;
2426
import java.util.LinkedHashMap;
2527
import java.util.List;
2628
import java.util.Map;
2729
import java.util.Set;
30+
import java.util.function.Function;
2831

2932
import javax.servlet.http.HttpServletRequest;
3033
import javax.servlet.http.HttpServletResponse;
3134

35+
import reactor.core.publisher.Flux;
36+
3237
import org.springframework.beans.factory.InitializingBean;
3338
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
3439
import org.springframework.boot.actuate.endpoint.InvocationContext;
@@ -52,6 +57,7 @@
5257
import org.springframework.http.server.ServletServerHttpRequest;
5358
import org.springframework.util.AntPathMatcher;
5459
import org.springframework.util.Assert;
60+
import org.springframework.util.ClassUtils;
5561
import org.springframework.util.ReflectionUtils;
5662
import org.springframework.util.StringUtils;
5763
import org.springframework.web.bind.annotation.RequestBody;
@@ -310,6 +316,17 @@ private static class ServletWebOperationAdapter implements ServletWebOperation {
310316

311317
private static final String PATH_SEPARATOR = AntPathMatcher.DEFAULT_PATH_SEPARATOR;
312318

319+
private static final List<Function<Object, Object>> BODY_CONVERTERS;
320+
321+
static {
322+
List<Function<Object, Object>> converters = new ArrayList<>();
323+
if (ClassUtils.isPresent("reactor.core.publisher.Flux",
324+
ServletWebOperationAdapter.class.getClassLoader())) {
325+
converters.add(new FluxBodyConverter());
326+
}
327+
BODY_CONVERTERS = Collections.unmodifiableList(converters);
328+
}
329+
313330
private final WebOperation operation;
314331

315332
ServletWebOperationAdapter(WebOperation operation) {
@@ -395,12 +412,32 @@ private Object handleResult(Object result, HttpMethod httpMethod) {
395412
(httpMethod != HttpMethod.GET) ? HttpStatus.NO_CONTENT : HttpStatus.NOT_FOUND);
396413
}
397414
if (!(result instanceof WebEndpointResponse)) {
398-
return result;
415+
return convertIfNecessary(result);
399416
}
400417
WebEndpointResponse<?> response = (WebEndpointResponse<?>) result;
401418
MediaType contentType = (response.getContentType() != null) ? new MediaType(response.getContentType())
402419
: null;
403-
return ResponseEntity.status(response.getStatus()).contentType(contentType).body(response.getBody());
420+
return ResponseEntity.status(response.getStatus()).contentType(contentType)
421+
.body(convertIfNecessary(response.getBody()));
422+
}
423+
424+
private Object convertIfNecessary(Object body) {
425+
for (Function<Object, Object> converter : BODY_CONVERTERS) {
426+
body = converter.apply(body);
427+
}
428+
return body;
429+
}
430+
431+
private static class FluxBodyConverter implements Function<Object, Object> {
432+
433+
@Override
434+
public Object apply(Object body) {
435+
if (!(body instanceof Flux)) {
436+
return body;
437+
}
438+
return ((Flux<?>) body).collectList();
439+
}
440+
404441
}
405442

406443
}

spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanTests.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import javax.management.ReflectionException;
2828

2929
import org.junit.jupiter.api.Test;
30+
import reactor.core.publisher.Flux;
3031
import reactor.core.publisher.Mono;
3132

3233
import org.springframework.beans.FatalBeanException;
@@ -155,6 +156,15 @@ void invokeWhenMonoResultShouldBlockOnMono() throws MBeanException, ReflectionEx
155156
assertThat(result).isEqualTo("monoResult");
156157
}
157158

159+
@Test
160+
void invokeWhenFluxResultShouldCollectToMonoListAndBlockOnMono() throws MBeanException, ReflectionException {
161+
TestExposableJmxEndpoint endpoint = new TestExposableJmxEndpoint(
162+
new TestJmxOperation((arguments) -> Flux.just("flux", "result")));
163+
EndpointMBean bean = new EndpointMBean(this.responseMapper, null, endpoint);
164+
Object result = bean.invoke("testOperation", NO_PARAMS, NO_SIGNATURE);
165+
assertThat(result).asList().containsExactly("flux", "result");
166+
}
167+
158168
@Test
159169
void invokeShouldCallResponseMapper() throws MBeanException, ReflectionException {
160170
TestJmxOperationResponseMapper responseMapper = spy(this.responseMapper);

spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/AbstractWebEndpointIntegrationTests.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.function.Supplier;
2929

3030
import org.junit.jupiter.api.Test;
31+
import reactor.core.publisher.Flux;
3132
import reactor.core.publisher.Mono;
3233

3334
import org.springframework.boot.actuate.endpoint.SecurityContext;
@@ -269,6 +270,14 @@ void readOperationWithMonoResponse() {
269270
.isOk().expectBody().jsonPath("a").isEqualTo("alpha"));
270271
}
271272

273+
@Test
274+
void readOperationWithFluxResponse() {
275+
load(FluxResponseEndpointConfiguration.class,
276+
(client) -> client.get().uri("/flux").exchange().expectStatus().isOk().expectBody().jsonPath("[0].a")
277+
.isEqualTo("alpha").jsonPath("[1].b").isEqualTo("bravo").jsonPath("[2].c")
278+
.isEqualTo("charlie"));
279+
}
280+
272281
@Test
273282
void readOperationWithCustomMediaType() {
274283
load(CustomMediaTypesEndpointConfiguration.class, (client) -> client.get().uri("/custommediatypes").exchange()
@@ -564,6 +573,17 @@ MonoResponseEndpoint testEndpoint(EndpointDelegate endpointDelegate) {
564573

565574
}
566575

576+
@Configuration(proxyBeanMethods = false)
577+
@Import(BaseConfiguration.class)
578+
static class FluxResponseEndpointConfiguration {
579+
580+
@Bean
581+
FluxResponseEndpoint testEndpoint(EndpointDelegate endpointDelegate) {
582+
return new FluxResponseEndpoint();
583+
}
584+
585+
}
586+
567587
@Configuration(proxyBeanMethods = false)
568588
@Import(BaseConfiguration.class)
569589
static class CustomMediaTypesEndpointConfiguration {
@@ -806,6 +826,17 @@ Mono<Map<String, String>> operation() {
806826

807827
}
808828

829+
@Endpoint(id = "flux")
830+
static class FluxResponseEndpoint {
831+
832+
@ReadOperation
833+
Flux<Map<String, String>> operation() {
834+
return Flux.just(Collections.singletonMap("a", "alpha"), Collections.singletonMap("b", "bravo"),
835+
Collections.singletonMap("c", "charlie"));
836+
}
837+
838+
}
839+
809840
@Endpoint(id = "custommediatypes")
810841
static class CustomMediaTypesEndpoint {
811842

0 commit comments

Comments
 (0)