diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java index e11c5aac55..4d1fa72b1c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java @@ -25,6 +25,7 @@ import org.junit.Test; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.actuate.endpoint.invoke.InvocationContext; import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper; import org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; @@ -57,7 +58,9 @@ public class CloudFoundryWebEndpointDiscovererTests { for (ExposableWebEndpoint endpoint : endpoints) { if (endpoint.getId().equals("health")) { WebOperation operation = endpoint.getOperations().iterator().next(); - assertThat(operation.invoke(Collections.emptyMap())).isEqualTo("cf"); + assertThat(operation + .invoke(new InvocationContext(null, Collections.emptyMap()))) + .isEqualTo("cf"); } } }); diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/Operation.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/Operation.java index f3462f9c74..a1af9eccce 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/Operation.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/Operation.java @@ -16,7 +16,7 @@ package org.springframework.boot.actuate.endpoint; -import java.util.Map; +import org.springframework.boot.actuate.endpoint.invoke.InvocationContext; /** * An operation on an {@link ExposableEndpoint endpoint}. @@ -34,10 +34,10 @@ public interface Operation { OperationType getType(); /** - * Invoke the underlying operation using the given {@code arguments}. - * @param arguments the arguments to pass to the operation + * Invoke the underlying operation using the given {@code context}. + * @param context the context in to use when invoking the operation * @return the result of the operation, may be {@code null} */ - Object invoke(Map arguments); + Object invoke(InvocationContext context); } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/AbstractDiscoveredOperation.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/AbstractDiscoveredOperation.java index 82b6843911..2c46c3270f 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/AbstractDiscoveredOperation.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/AbstractDiscoveredOperation.java @@ -16,10 +16,9 @@ package org.springframework.boot.actuate.endpoint.annotation; -import java.util.Map; - import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.boot.actuate.endpoint.OperationType; +import org.springframework.boot.actuate.endpoint.invoke.InvocationContext; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.invoke.reflect.OperationMethod; import org.springframework.core.style.ToStringCreator; @@ -58,8 +57,8 @@ public abstract class AbstractDiscoveredOperation implements Operation { } @Override - public Object invoke(Map arguments) { - return this.invoker.invoke(arguments); + public Object invoke(InvocationContext context) { + return this.invoker.invoke(context); } @Override diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/InvocationContext.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/InvocationContext.java new file mode 100644 index 0000000000..9d758c2a2d --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/InvocationContext.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.endpoint.invoke; + +import java.security.Principal; +import java.util.Map; + +import org.springframework.util.Assert; + +/** + * The context for the {@link OperationInvoker invocation of an operation}. + * + * @author Andy Wilkinson + * @since 2.0.0 + */ +public class InvocationContext { + + private final Principal principal; + + private final Map arguments; + + /** + * Creates a new context for an operation being invoked by the given {@code principal} + * with the given available {@code arguments}. + * + * @param principal the principal invoking the operation. May be {@code null} + * @param arguments the arguments available to the operation. Never {@code null} + */ + public InvocationContext(Principal principal, Map arguments) { + Assert.notNull(arguments, "Arguments must not be null"); + this.principal = principal; + this.arguments = arguments; + } + + public Principal getPrincipal() { + return this.principal; + } + + public Map getArguments() { + return this.arguments; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/OperationInvoker.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/OperationInvoker.java index a2031df145..842ef7656c 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/OperationInvoker.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/OperationInvoker.java @@ -16,8 +16,6 @@ package org.springframework.boot.actuate.endpoint.invoke; -import java.util.Map; - /** * Interface to perform an operation invocation. * @@ -29,11 +27,11 @@ import java.util.Map; public interface OperationInvoker { /** - * Invoke the underlying operation using the given {@code arguments}. - * @param arguments the arguments to pass to the operation + * Invoke the underlying operation using the given {@code context}. + * @param context the context to use to invoke the operation * @return the result of the operation, may be {@code null} * @throws MissingParametersException if parameters are missing */ - Object invoke(Map arguments) throws MissingParametersException; + Object invoke(InvocationContext context) throws MissingParametersException; } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/reflect/ReflectiveOperationInvoker.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/reflect/ReflectiveOperationInvoker.java index fff55074bc..93078a218a 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/reflect/ReflectiveOperationInvoker.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/reflect/ReflectiveOperationInvoker.java @@ -17,10 +17,11 @@ package org.springframework.boot.actuate.endpoint.invoke.reflect; import java.lang.reflect.Method; -import java.util.Map; +import java.security.Principal; import java.util.Set; import java.util.stream.Collectors; +import org.springframework.boot.actuate.endpoint.invoke.InvocationContext; import org.springframework.boot.actuate.endpoint.invoke.MissingParametersException; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.invoke.OperationParameter; @@ -66,39 +67,44 @@ public class ReflectiveOperationInvoker implements OperationInvoker { } @Override - public Object invoke(Map arguments) { - validateRequiredParameters(arguments); + public Object invoke(InvocationContext context) { + validateRequiredParameters(context); Method method = this.operationMethod.getMethod(); - Object[] resolvedArguments = resolveArguments(arguments); + Object[] resolvedArguments = resolveArguments(context); ReflectionUtils.makeAccessible(method); return ReflectionUtils.invokeMethod(method, this.target, resolvedArguments); } - private void validateRequiredParameters(Map arguments) { + private void validateRequiredParameters(InvocationContext context) { Set missing = this.operationMethod.getParameters().stream() - .filter((parameter) -> isMissing(arguments, parameter)) + .filter((parameter) -> isMissing(context, parameter)) .collect(Collectors.toSet()); if (!missing.isEmpty()) { throw new MissingParametersException(missing); } } - private boolean isMissing(Map arguments, - OperationParameter parameter) { + private boolean isMissing(InvocationContext context, OperationParameter parameter) { if (!parameter.isMandatory()) { return false; } - return arguments.get(parameter.getName()) == null; + if (Principal.class.equals(parameter.getType())) { + return context.getPrincipal() == null; + } + return context.getArguments().get(parameter.getName()) == null; } - private Object[] resolveArguments(Map arguments) { + private Object[] resolveArguments(InvocationContext context) { return this.operationMethod.getParameters().stream() - .map((parameter) -> resolveArgument(parameter, arguments)).toArray(); + .map((parameter) -> resolveArgument(parameter, context)).toArray(); } private Object resolveArgument(OperationParameter parameter, - Map arguments) { - Object value = arguments.get(parameter.getName()); + InvocationContext context) { + if (Principal.class.equals(parameter.getType())) { + return context.getPrincipal(); + } + Object value = context.getArguments().get(parameter.getName()); return this.parameterValueMapper.mapParameterValue(parameter, value); } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvoker.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvoker.java index 2668bfc98d..a9370f7655 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvoker.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvoker.java @@ -19,6 +19,7 @@ package org.springframework.boot.actuate.endpoint.invoker.cache; import java.util.Map; import java.util.Objects; +import org.springframework.boot.actuate.endpoint.invoke.InvocationContext; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -59,21 +60,25 @@ public class CachingOperationInvoker implements OperationInvoker { } @Override - public Object invoke(Map arguments) { - if (hasArgument(arguments)) { - return this.invoker.invoke(arguments); + public Object invoke(InvocationContext context) { + if (hasInput(context)) { + return this.invoker.invoke(context); } long accessTime = System.currentTimeMillis(); CachedResponse cached = this.cachedResponse; if (cached == null || cached.isStale(accessTime, this.timeToLive)) { - Object response = this.invoker.invoke(arguments); + Object response = this.invoker.invoke(context); this.cachedResponse = new CachedResponse(response, accessTime); return response; } return cached.getResponse(); } - private boolean hasArgument(Map arguments) { + private boolean hasInput(InvocationContext context) { + if (context.getPrincipal() != null) { + return true; + } + Map arguments = context.getArguments(); if (!ObjectUtils.isEmpty(arguments)) { return arguments.values().stream().anyMatch(Objects::nonNull); } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBean.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBean.java index c15abfe146..8f3e7267c0 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBean.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBean.java @@ -32,6 +32,7 @@ import javax.management.ReflectionException; import reactor.core.publisher.Mono; import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException; +import org.springframework.boot.actuate.endpoint.invoke.InvocationContext; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -96,7 +97,7 @@ public class EndpointMBean implements DynamicMBean { String[] parameterNames = operation.getParameters().stream() .map(JmxOperationParameter::getName).toArray(String[]::new); Map arguments = getArguments(parameterNames, params); - Object result = operation.invoke(arguments); + Object result = operation.invoke(new InvocationContext(null, arguments)); if (REACTOR_PRESENT) { result = ReactiveHandler.handle(result); } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/jersey/JerseyEndpointResourceFactory.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/jersey/JerseyEndpointResourceFactory.java index 19fa276241..e7e2e019f1 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/jersey/JerseyEndpointResourceFactory.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/jersey/JerseyEndpointResourceFactory.java @@ -18,7 +18,6 @@ package org.springframework.boot.actuate.endpoint.web.jersey; import java.io.IOException; import java.io.InputStream; -import java.security.Principal; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -40,6 +39,7 @@ import org.glassfish.jersey.server.model.Resource.Builder; import reactor.core.publisher.Mono; import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException; +import org.springframework.boot.actuate.endpoint.invoke.InvocationContext; import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; @@ -148,12 +148,9 @@ public class JerseyEndpointResourceFactory { } arguments.putAll(extractPathParameters(data)); arguments.putAll(extractQueryParameters(data)); - Principal principal = data.getSecurityContext().getUserPrincipal(); - if (principal != null) { - arguments.put("principal", principal); - } try { - Object response = this.operation.invoke(arguments); + Object response = this.operation.invoke(new InvocationContext( + data.getSecurityContext().getUserPrincipal(), arguments)); return convertToJaxRsResponse(response, data.getRequest().getMethod()); } catch (InvalidEndpointRequestException ex) { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMapping.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMapping.java index a319017581..00ffffd01e 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMapping.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMapping.java @@ -29,6 +29,7 @@ import reactor.core.scheduler.Schedulers; import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException; import org.springframework.boot.actuate.endpoint.OperationType; +import org.springframework.boot.actuate.endpoint.invoke.InvocationContext; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; @@ -221,14 +222,14 @@ public abstract class AbstractWebFluxEndpointHandlerMapping } @Override - public Object invoke(Map arguments) { - return Mono.create((sink) -> Schedulers.elastic() - .schedule(() -> invoke(arguments, sink))); + public Object invoke(InvocationContext context) { + return Mono.create( + (sink) -> Schedulers.elastic().schedule(() -> invoke(context, sink))); } - private void invoke(Map arguments, MonoSink sink) { + private void invoke(InvocationContext context, MonoSink sink) { try { - Object result = this.invoker.invoke(arguments); + Object result = this.invoker.invoke(context); sink.success(result); } catch (Exception ex) { @@ -275,15 +276,17 @@ public abstract class AbstractWebFluxEndpointHandlerMapping Map body) { return exchange.getPrincipal().defaultIfEmpty(NO_PRINCIPAL) .flatMap((principal) -> { - Map arguments = getArguments(exchange, principal, - body); - return handleResult((Publisher) this.invoker.invoke(arguments), + Map arguments = getArguments(exchange, body); + return handleResult( + (Publisher) this.invoker.invoke(new InvocationContext( + principal == NO_PRINCIPAL ? null : principal, + arguments)), exchange.getRequest().getMethod()); }); } private Map getArguments(ServerWebExchange exchange, - Principal principal, Map body) { + Map body) { Map arguments = new LinkedHashMap<>(); arguments.putAll(getTemplateVariables(exchange)); if (body != null) { @@ -291,9 +294,6 @@ public abstract class AbstractWebFluxEndpointHandlerMapping } exchange.getRequest().getQueryParams().forEach((name, values) -> arguments .put(name, values.size() == 1 ? values.get(0) : values)); - if (principal != null && principal != NO_PRINCIPAL) { - arguments.put("principal", principal); - } return arguments; } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/AbstractWebMvcEndpointHandlerMapping.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/AbstractWebMvcEndpointHandlerMapping.java index 175c7151f1..f46d3ffa23 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/AbstractWebMvcEndpointHandlerMapping.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/AbstractWebMvcEndpointHandlerMapping.java @@ -17,7 +17,6 @@ package org.springframework.boot.actuate.endpoint.web.servlet; import java.lang.reflect.Method; -import java.security.Principal; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; @@ -29,6 +28,7 @@ import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.InitializingBean; import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException; +import org.springframework.boot.actuate.endpoint.invoke.InvocationContext; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; @@ -241,7 +241,9 @@ public abstract class AbstractWebMvcEndpointHandlerMapping @RequestBody(required = false) Map body) { Map arguments = getArguments(request, body); try { - return handleResult(this.invoker.invoke(arguments), + return handleResult( + this.invoker.invoke(new InvocationContext( + request.getUserPrincipal(), arguments)), HttpMethod.valueOf(request.getMethod())); } catch (InvalidEndpointRequestException ex) { @@ -258,10 +260,6 @@ public abstract class AbstractWebMvcEndpointHandlerMapping } request.getParameterMap().forEach((name, values) -> arguments.put(name, values.length == 1 ? values[0] : Arrays.asList(values))); - Principal principal = request.getUserPrincipal(); - if (principal != null) { - arguments.put("principal", principal); - } return arguments; } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/DiscoveredOperationsFactoryTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/DiscoveredOperationsFactoryTests.java index 20b571fcbb..aa52c48a46 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/DiscoveredOperationsFactoryTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/DiscoveredOperationsFactoryTests.java @@ -26,6 +26,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.boot.actuate.endpoint.OperationType; +import org.springframework.boot.actuate.endpoint.invoke.InvocationContext; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor; import org.springframework.boot.actuate.endpoint.invoke.OperationParameters; @@ -105,7 +106,7 @@ public class DiscoveredOperationsFactoryTests { TestOperation operation = getFirst( this.factory.createOperations("test", new ExampleWithParams())); Map params = Collections.singletonMap("name", 123); - Object result = operation.invoke(params); + Object result = operation.invoke(new InvocationContext(null, params)); assertThat(result).isEqualTo("123"); } @@ -115,7 +116,7 @@ public class DiscoveredOperationsFactoryTests { this.invokerAdvisors.add(advisor); TestOperation operation = getFirst( this.factory.createOperations("test", new ExampleRead())); - operation.invoke(Collections.emptyMap()); + operation.invoke(new InvocationContext(null, Collections.emptyMap())); assertThat(advisor.getEndpointId()).isEqualTo("test"); assertThat(advisor.getOperationType()).isEqualTo(OperationType.READ); assertThat(advisor.getParameters()).isEmpty(); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoke/reflect/ReflectiveOperationInvokerTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoke/reflect/ReflectiveOperationInvokerTests.java index c6e14c7a55..ba425dfeb3 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoke/reflect/ReflectiveOperationInvokerTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoke/reflect/ReflectiveOperationInvokerTests.java @@ -24,6 +24,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.boot.actuate.endpoint.OperationType; +import org.springframework.boot.actuate.endpoint.invoke.InvocationContext; import org.springframework.boot.actuate.endpoint.invoke.MissingParametersException; import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; import org.springframework.lang.Nullable; @@ -83,7 +84,8 @@ public class ReflectiveOperationInvokerTests { public void invokeShouldInvokeMethod() { ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(this.target, this.operationMethod, this.parameterValueMapper); - Object result = invoker.invoke(Collections.singletonMap("name", "boot")); + Object result = invoker.invoke( + new InvocationContext(null, Collections.singletonMap("name", "boot"))); assertThat(result).isEqualTo("toob"); } @@ -92,7 +94,8 @@ public class ReflectiveOperationInvokerTests { ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(this.target, this.operationMethod, this.parameterValueMapper); this.thrown.expect(MissingParametersException.class); - invoker.invoke(Collections.singletonMap("name", null)); + invoker.invoke( + new InvocationContext(null, Collections.singletonMap("name", null))); } @Test @@ -101,7 +104,8 @@ public class ReflectiveOperationInvokerTests { Example.class, "reverseNullable", String.class), OperationType.READ); ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(this.target, operationMethod, this.parameterValueMapper); - Object result = invoker.invoke(Collections.singletonMap("name", null)); + Object result = invoker.invoke( + new InvocationContext(null, Collections.singletonMap("name", null))); assertThat(result).isEqualTo("llun"); } @@ -109,7 +113,8 @@ public class ReflectiveOperationInvokerTests { public void invokeShouldResolveParameters() { ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(this.target, this.operationMethod, this.parameterValueMapper); - Object result = invoker.invoke(Collections.singletonMap("name", 1234)); + Object result = invoker.invoke( + new InvocationContext(null, Collections.singletonMap("name", 1234))); assertThat(result).isEqualTo("4321"); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerTests.java index d8a37c228c..d36f649aea 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerTests.java @@ -24,6 +24,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.springframework.boot.actuate.endpoint.invoke.InvocationContext; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import static org.assertj.core.api.Assertions.assertThat; @@ -66,12 +67,13 @@ public class CachingOperationInvokerTests { private void assertCacheIsUsed(Map parameters) { OperationInvoker target = mock(OperationInvoker.class); Object expected = new Object(); - given(target.invoke(parameters)).willReturn(expected); + InvocationContext context = new InvocationContext(null, parameters); + given(target.invoke(context)).willReturn(expected); CachingOperationInvoker invoker = new CachingOperationInvoker(target, 500L); - Object response = invoker.invoke(parameters); + Object response = invoker.invoke(context); assertThat(response).isSameAs(expected); - verify(target, times(1)).invoke(parameters); - Object cachedResponse = invoker.invoke(parameters); + verify(target, times(1)).invoke(context); + Object cachedResponse = invoker.invoke(context); assertThat(cachedResponse).isSameAs(response); verifyNoMoreInteractions(target); } @@ -82,24 +84,26 @@ public class CachingOperationInvokerTests { Map parameters = new HashMap<>(); parameters.put("test", "value"); parameters.put("something", null); - given(target.invoke(parameters)).willReturn(new Object()); + InvocationContext context = new InvocationContext(null, parameters); + given(target.invoke(context)).willReturn(new Object()); CachingOperationInvoker invoker = new CachingOperationInvoker(target, 500L); - invoker.invoke(parameters); - invoker.invoke(parameters); - invoker.invoke(parameters); - verify(target, times(3)).invoke(parameters); + invoker.invoke(context); + invoker.invoke(context); + invoker.invoke(context); + verify(target, times(3)).invoke(context); } @Test public void targetInvokedWhenCacheExpires() throws InterruptedException { OperationInvoker target = mock(OperationInvoker.class); Map parameters = new HashMap<>(); - given(target.invoke(parameters)).willReturn(new Object()); + InvocationContext context = new InvocationContext(null, parameters); + given(target.invoke(context)).willReturn(new Object()); CachingOperationInvoker invoker = new CachingOperationInvoker(target, 50L); - invoker.invoke(parameters); + invoker.invoke(context); Thread.sleep(55); - invoker.invoke(parameters); - verify(target, times(2)).invoke(parameters); + invoker.invoke(context); + verify(target, times(2)).invoke(context); } } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/TestJmxOperation.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/TestJmxOperation.java index 56c387cd71..0eef882db2 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/TestJmxOperation.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/TestJmxOperation.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.function.Function; import org.springframework.boot.actuate.endpoint.OperationType; +import org.springframework.boot.actuate.endpoint.invoke.InvocationContext; /** * Test {@link JmxOperation} implementation. @@ -66,8 +67,9 @@ public class TestJmxOperation implements JmxOperation { } @Override - public Object invoke(Map arguments) { - return (this.invoke == null ? "result" : this.invoke.apply(arguments)); + public Object invoke(InvocationContext context) { + return (this.invoke == null ? "result" + : this.invoke.apply(context.getArguments())); } @Override diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/AbstractWebEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/AbstractWebEndpointIntegrationTests.java index 4c0c87fc1d..349896f507 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/AbstractWebEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/AbstractWebEndpointIntegrationTests.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Map; import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.function.Supplier; import org.junit.Test; import reactor.core.publisher.Mono; @@ -37,6 +38,7 @@ import org.springframework.boot.actuate.endpoint.annotation.WriteOperation; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigRegistry; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -57,7 +59,7 @@ import static org.mockito.Mockito.verify; * @param the type of application context used by the tests * @author Andy Wilkinson */ -public abstract class AbstractWebEndpointIntegrationTests { +public abstract class AbstractWebEndpointIntegrationTests { private static final Duration TIMEOUT = Duration.ofMinutes(6); @@ -65,10 +67,14 @@ public abstract class AbstractWebEndpointIntegrationTests exporterConfiguration; + private final Supplier applicationContextSupplier; - protected AbstractWebEndpointIntegrationTests(Class exporterConfiguration) { - this.exporterConfiguration = exporterConfiguration; + private final Consumer authenticatedContextCustomizer; + + protected AbstractWebEndpointIntegrationTests(Supplier applicationContextSupplier, + Consumer authenticatedContextCustomizer) { + this.applicationContextSupplier = applicationContextSupplier; + this.authenticatedContextCustomizer = authenticatedContextCustomizer; } @Test @@ -337,13 +343,23 @@ public abstract class AbstractWebEndpointIntegrationTests client.get().uri("/principal") - .accept(MediaType.APPLICATION_JSON).exchange().expectStatus() - .isOk().expectBody(String.class).isEqualTo("Alice")); + load((context) -> { + this.authenticatedContextCustomizer.accept(context); + context.register(PrincipalEndpointConfiguration.class); + }, (client) -> client.get().uri("/principal").accept(MediaType.APPLICATION_JSON) + .exchange().expectStatus().isOk().expectBody(String.class) + .isEqualTo("Alice")); } - protected abstract T createApplicationContext(Class... config); + @Test + public void operationWithAQueryNamedPrincipalCanBeAccessedWhenAuthenticated() { + load((context) -> { + this.authenticatedContextCustomizer.accept(context); + context.register(PrincipalQueryEndpointConfiguration.class); + }, (client) -> client.get().uri("/principalquery?principal=Zoe") + .accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk() + .expectBody(String.class).isEqualTo("Zoe")); + } protected abstract int getPort(T context); @@ -356,40 +372,47 @@ public abstract class AbstractWebEndpointIntegrationTests configuration, BiConsumer consumer) { - load(configuration, "/endpoints", consumer); + load((context) -> context.register(configuration), "/endpoints", consumer); } - private void load(Class configuration, String endpointPath, - BiConsumer consumer) { - T context = createApplicationContext(configuration, this.exporterConfiguration); - context.getEnvironment().getPropertySources().addLast(new MapPropertySource( - "test", Collections.singletonMap("endpointPath", endpointPath))); - context.refresh(); - try { - InetSocketAddress address = new InetSocketAddress(getPort(context)); - String url = "http://" + address.getHostString() + ":" + address.getPort() - + endpointPath; - consumer.accept(context, WebTestClient.bindToServer().baseUrl(url) - .responseTimeout(TIMEOUT).build()); - } - finally { - context.close(); - } - } - - protected abstract Class getSecuredPrincipalEndpointConfiguration(); - protected void load(Class configuration, Consumer clientConsumer) { - load(configuration, "/endpoints", + load((context) -> context.register(configuration), "/endpoints", + (context, client) -> clientConsumer.accept(client)); + } + + protected void load(Consumer contextCustomizer, + Consumer clientConsumer) { + load(contextCustomizer, "/endpoints", (context, client) -> clientConsumer.accept(client)); } protected void load(Class configuration, String endpointPath, Consumer clientConsumer) { - load(configuration, endpointPath, + load((context) -> context.register(configuration), endpointPath, (context, client) -> clientConsumer.accept(client)); } + private void load(Consumer contextCustomizer, String endpointPath, + BiConsumer consumer) { + T applicationContext = this.applicationContextSupplier.get(); + contextCustomizer.accept(applicationContext); + applicationContext.getEnvironment().getPropertySources() + .addLast(new MapPropertySource("test", + Collections.singletonMap("endpointPath", endpointPath))); + applicationContext.refresh(); + try { + InetSocketAddress address = new InetSocketAddress( + getPort(applicationContext)); + String url = "http://" + address.getHostString() + ":" + address.getPort() + + endpointPath; + consumer.accept(applicationContext, WebTestClient.bindToServer().baseUrl(url) + .responseTimeout(TIMEOUT).build()); + } + finally { + applicationContext.close(); + } + } + @Configuration @Import(BaseConfiguration.class) protected static class TestEndpointConfiguration { @@ -547,6 +570,17 @@ public abstract class AbstractWebEndpointIntegrationTests { public JerseyWebEndpointIntegrationTests() { - super(JerseyConfiguration.class); + super(JerseyWebEndpointIntegrationTests::createApplicationContext, + JerseyWebEndpointIntegrationTests::applyAuthenticatedConfiguration); } - @Override - protected AnnotationConfigServletWebServerApplicationContext createApplicationContext( - Class... config) { + private static AnnotationConfigServletWebServerApplicationContext createApplicationContext() { AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(); - context.register(config); + context.register(JerseyConfiguration.class); return context; } + private static void applyAuthenticatedConfiguration( + AnnotationConfigServletWebServerApplicationContext context) { + context.register(AuthenticatedConfiguration.class); + } + @Override protected int getPort(AnnotationConfigServletWebServerApplicationContext context) { return context.getWebServer().getPort(); @@ -83,11 +86,6 @@ public class JerseyWebEndpointIntegrationTests extends // Jersey doesn't support the general error page handling } - @Override - protected Class getSecuredPrincipalEndpointConfiguration() { - return SecuredPrincipalEndpointConfiguration.class; - } - @Configuration static class JerseyConfiguration { @@ -123,8 +121,7 @@ public class JerseyWebEndpointIntegrationTests extends } @Configuration - @Import(PrincipalEndpointConfiguration.class) - static class SecuredPrincipalEndpointConfiguration { + static class AuthenticatedConfiguration { @Bean public Filter securityFilter() { diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/WebFluxEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/WebFluxEndpointIntegrationTests.java index ff33eb8105..8124fe1d01 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/WebFluxEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/WebFluxEndpointIntegrationTests.java @@ -31,13 +31,11 @@ import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration; import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext; -import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext; import org.springframework.boot.web.reactive.context.ReactiveWebServerInitializedEvent; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.core.env.Environment; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -58,11 +56,24 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Andy Wilkinson * @see WebFluxEndpointHandlerMapping */ -public class WebFluxEndpointIntegrationTests - extends AbstractWebEndpointIntegrationTests { +public class WebFluxEndpointIntegrationTests extends + AbstractWebEndpointIntegrationTests { public WebFluxEndpointIntegrationTests() { - super(ReactiveConfiguration.class); + super(WebFluxEndpointIntegrationTests::createApplicationContext, + WebFluxEndpointIntegrationTests::applyAuthenticatedConfiguration); + + } + + private static AnnotationConfigReactiveWebServerApplicationContext createApplicationContext() { + AnnotationConfigReactiveWebServerApplicationContext context = new AnnotationConfigReactiveWebServerApplicationContext(); + context.register(ReactiveConfiguration.class); + return context; + } + + private static void applyAuthenticatedConfiguration( + AnnotationConfigReactiveWebServerApplicationContext context) { + context.register(AuthenticatedConfiguration.class); } @Test @@ -89,23 +100,10 @@ public class WebFluxEndpointIntegrationTests } @Override - protected AnnotationConfigReactiveWebServerApplicationContext createApplicationContext( - Class... config) { - AnnotationConfigReactiveWebServerApplicationContext context = new AnnotationConfigReactiveWebServerApplicationContext(); - context.register(config); - return context; - } - - @Override - protected int getPort(ReactiveWebServerApplicationContext context) { + protected int getPort(AnnotationConfigReactiveWebServerApplicationContext context) { return context.getBean(ReactiveConfiguration.class).port; } - @Override - protected Class getSecuredPrincipalEndpointConfiguration() { - return SecuredPrincipalEndpointConfiguration.class; - } - @Configuration @EnableWebFlux @ImportAutoConfiguration(ErrorWebFluxAutoConfiguration.class) @@ -144,8 +142,8 @@ public class WebFluxEndpointIntegrationTests } - @Import(PrincipalEndpointConfiguration.class) - static class SecuredPrincipalEndpointConfiguration { + @Configuration + static class AuthenticatedConfiguration { @Bean public WebFilter webFilter() { diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/MvcWebEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/MvcWebEndpointIntegrationTests.java index 43995e74db..962afb984e 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/MvcWebEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/MvcWebEndpointIntegrationTests.java @@ -45,7 +45,6 @@ import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactor import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.core.env.Environment; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -64,7 +63,19 @@ public class MvcWebEndpointIntegrationTests extends AbstractWebEndpointIntegrationTests { public MvcWebEndpointIntegrationTests() { - super(WebMvcConfiguration.class); + super(MvcWebEndpointIntegrationTests::createApplicationContext, + MvcWebEndpointIntegrationTests::applyAuthenticatedConfiguration); + } + + private static AnnotationConfigServletWebServerApplicationContext createApplicationContext() { + AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(); + context.register(WebMvcConfiguration.class); + return context; + } + + private static void applyAuthenticatedConfiguration( + AnnotationConfigServletWebServerApplicationContext context) { + context.register(AuthenticatedConfiguration.class); } @Test @@ -90,24 +101,11 @@ public class MvcWebEndpointIntegrationTests extends }); } - @Override - protected AnnotationConfigServletWebServerApplicationContext createApplicationContext( - Class... config) { - AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(); - context.register(config); - return context; - } - @Override protected int getPort(AnnotationConfigServletWebServerApplicationContext context) { return context.getWebServer().getPort(); } - @Override - protected Class getSecuredPrincipalEndpointConfiguration() { - return SecuredPrincipalEndpointConfiguration.class; - } - @Configuration @ImportAutoConfiguration({ JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, @@ -137,8 +135,7 @@ public class MvcWebEndpointIntegrationTests extends } @Configuration - @Import(PrincipalEndpointConfiguration.class) - static class SecuredPrincipalEndpointConfiguration { + static class AuthenticatedConfiguration { @Bean public Filter securityFilter() {