mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-09-03 04:26:12 +08:00
Overhaul actuator endpoint code
Refactor several areas of the actuator endpoint code in order to make future extensions easier. The primary goal is to introduce the concept of an `ExposableEndpoint` that has technology specific subclasses and can carry additional data for filters to use. Many other changes have been made along the way including: * A new EndpointSupplier interface that allows cleaner separation of supplying vs discovering endpoints. This allows cleaner class names and allows for better auto-configuration since a user can choose to provide their own supplier entirely. * A `DiscoveredEndpoint` interface that allows the `EndpointFilter` to be greatly simplified. A filter now doesn't need to know about discovery concerns unless absolutely necessary. * Improved naming and package structure. Many technology specific concerns are now grouped in a better way. Related concerns are co-located and concepts from one area no longer leakage into another. * Simplified `HandlerMapping` implementations. Many common concerns have been pulled up helping to create simpler subclasses. * Simplified JMX adapters. Many of the intermediary `Info` classes have been removed. The `DiscoveredJmxOperation` is now responsible for mapping methods to operations. * A specific @`HealthEndpointCloudFoundryExtension` for Cloud Foundry. The extension logic used to create a "full" health endpoint extension has been made explicit. Fixes gh-11428 Fixes gh-11581
This commit is contained in:
parent
dc935fba48
commit
1d39feffea
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -40,19 +40,19 @@ public enum AccessLevel {
|
||||
|
||||
public static final String REQUEST_ATTRIBUTE = "cloudFoundryAccessLevel";
|
||||
|
||||
private final List<String> endpointPaths;
|
||||
private final List<String> endpointIds;
|
||||
|
||||
AccessLevel(String... endpointPaths) {
|
||||
this.endpointPaths = Arrays.asList(endpointPaths);
|
||||
AccessLevel(String... endpointIds) {
|
||||
this.endpointIds = Arrays.asList(endpointIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the access level should allow access to the specified endpoint path.
|
||||
* @param endpointPath the endpoint path
|
||||
* @param endpointId the endpoint ID to check
|
||||
* @return {@code true} if access is allowed
|
||||
*/
|
||||
public boolean isAccessAllowed(String endpointPath) {
|
||||
return this.endpointPaths.isEmpty() || this.endpointPaths.contains(endpointPath);
|
||||
public boolean isAccessAllowed(String endpointId) {
|
||||
return this.endpointIds.isEmpty() || this.endpointIds.contains(endpointId);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,22 +16,19 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.cloudfoundry;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointFilter;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.DiscovererEndpointFilter;
|
||||
|
||||
/**
|
||||
* {@link EndpointFilter} for endpoints discovered by
|
||||
* {@link CloudFoundryWebAnnotationEndpointDiscoverer}.
|
||||
* {@link CloudFoundryWebEndpointDiscoverer}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class CloudFoundryEndpointFilter implements EndpointFilter<WebOperation> {
|
||||
class CloudFoundryEndpointFilter extends DiscovererEndpointFilter {
|
||||
|
||||
@Override
|
||||
public boolean match(EndpointInfo<WebOperation> info, EndpointDiscoverer<WebOperation> discoverer) {
|
||||
return (discoverer instanceof CloudFoundryWebAnnotationEndpointDiscoverer);
|
||||
protected CloudFoundryEndpointFilter() {
|
||||
super(CloudFoundryWebEndpointDiscoverer.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,66 +0,0 @@
|
||||
/*
|
||||
* 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.autoconfigure.cloudfoundry;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointFilter;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInvokerAdvisor;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* {@link WebAnnotationEndpointDiscoverer} for Cloud Foundry that uses Cloud Foundry
|
||||
* specific extensions for the {@link HealthEndpoint}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class CloudFoundryWebAnnotationEndpointDiscoverer
|
||||
extends WebAnnotationEndpointDiscoverer {
|
||||
|
||||
private final Class<?> requiredExtensionType;
|
||||
|
||||
public CloudFoundryWebAnnotationEndpointDiscoverer(
|
||||
ApplicationContext applicationContext, ParameterMapper parameterMapper,
|
||||
EndpointMediaTypes endpointMediaTypes,
|
||||
EndpointPathResolver endpointPathResolver,
|
||||
Collection<? extends OperationMethodInvokerAdvisor> invokerAdvisors,
|
||||
Collection<? extends EndpointFilter<WebOperation>> filters,
|
||||
Class<?> requiredExtensionType) {
|
||||
super(applicationContext, parameterMapper, endpointMediaTypes,
|
||||
endpointPathResolver, invokerAdvisors, filters);
|
||||
this.requiredExtensionType = requiredExtensionType;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isExtensionExposed(Class<?> endpointType, Class<?> extensionType,
|
||||
EndpointInfo<WebOperation> endpointInfo) {
|
||||
if (HealthEndpoint.class.equals(endpointType)
|
||||
&& !this.requiredExtensionType.equals(extensionType)) {
|
||||
return false;
|
||||
}
|
||||
return super.isExtensionExposed(endpointType, extensionType, endpointInfo);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.autoconfigure.cloudfoundry;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointFilter;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
|
||||
/**
|
||||
* {@link WebEndpointDiscoverer} for Cloud Foundry that uses Cloud Foundry specific
|
||||
* extensions for the {@link HealthEndpoint}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class CloudFoundryWebEndpointDiscoverer extends WebEndpointDiscoverer {
|
||||
|
||||
/**
|
||||
* Create a new {@link WebEndpointDiscoverer} instance.
|
||||
* @param applicationContext the source application context
|
||||
* @param parameterValueMapper the parameter value mapper
|
||||
* @param endpointMediaTypes the endpoint media types
|
||||
* @param endpointPathResolver the endpoint path resolver
|
||||
* @param invokerAdvisors invoker advisors to apply
|
||||
* @param filters filters to apply
|
||||
*/
|
||||
public CloudFoundryWebEndpointDiscoverer(ApplicationContext applicationContext,
|
||||
ParameterValueMapper parameterValueMapper,
|
||||
EndpointMediaTypes endpointMediaTypes,
|
||||
EndpointPathResolver endpointPathResolver,
|
||||
Collection<OperationInvokerAdvisor> invokerAdvisors,
|
||||
Collection<EndpointFilter<ExposableWebEndpoint>> filters) {
|
||||
super(applicationContext, parameterValueMapper, endpointMediaTypes,
|
||||
endpointPathResolver, invokerAdvisors, filters);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isExtensionExposed(Object extensionBean) {
|
||||
if (isHealthEndpointExtension(extensionBean)
|
||||
&& !isCloudFoundryHealthEndpointExtension(extensionBean)) {
|
||||
// Filter regular health endpoint extensions so a CF version can replace them
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isHealthEndpointExtension(Object extensionBean) {
|
||||
AnnotationAttributes attributes = AnnotatedElementUtils
|
||||
.getMergedAnnotationAttributes(extensionBean.getClass(),
|
||||
EndpointWebExtension.class);
|
||||
Class<?> endpoint = (attributes == null ? null : attributes.getClass("endpoint"));
|
||||
return (endpoint != null && HealthEndpoint.class.isAssignableFrom(endpoint));
|
||||
}
|
||||
|
||||
private boolean isCloudFoundryHealthEndpointExtension(Object extensionBean) {
|
||||
return AnnotatedElementUtils.hasAnnotation(extensionBean.getClass(),
|
||||
HealthEndpointCloudFoundryExtension.class);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.autoconfigure.cloudfoundry;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension;
|
||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||
|
||||
/**
|
||||
* Identifies a type as being a Cloud Foundry specific extension for the
|
||||
* {@link HealthEndpoint}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@EndpointExtension(filter = CloudFoundryEndpointFilter.class, endpoint = HealthEndpoint.class)
|
||||
public @interface HealthEndpointCloudFoundryExtension {
|
||||
|
||||
}
|
@ -18,7 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryEndpointFilter;
|
||||
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.HealthEndpointCloudFoundryExtension;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
@ -33,7 +33,7 @@ import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtensio
|
||||
* @author Madhura Bhave
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@EndpointExtension(filter = CloudFoundryEndpointFilter.class, endpoint = HealthEndpoint.class)
|
||||
@HealthEndpointCloudFoundryExtension
|
||||
public class CloudFoundryReactiveHealthEndpointWebExtension {
|
||||
|
||||
private final ReactiveHealthEndpointWebExtension delegate;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -35,10 +35,10 @@ import org.springframework.web.server.ServerWebExchange;
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class ReactiveCloudFoundrySecurityInterceptor {
|
||||
class CloudFoundrySecurityInterceptor {
|
||||
|
||||
private static final Log logger = LogFactory
|
||||
.getLog(ReactiveCloudFoundrySecurityInterceptor.class);
|
||||
.getLog(CloudFoundrySecurityInterceptor.class);
|
||||
|
||||
private final ReactiveTokenValidator tokenValidator;
|
||||
|
||||
@ -48,7 +48,7 @@ class ReactiveCloudFoundrySecurityInterceptor {
|
||||
|
||||
private static Mono<SecurityResponse> SUCCESS = Mono.just(SecurityResponse.success());
|
||||
|
||||
ReactiveCloudFoundrySecurityInterceptor(ReactiveTokenValidator tokenValidator,
|
||||
CloudFoundrySecurityInterceptor(ReactiveTokenValidator tokenValidator,
|
||||
ReactiveCloudFoundrySecurityService cloudFoundrySecurityService,
|
||||
String applicationId) {
|
||||
this.tokenValidator = tokenValidator;
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -16,10 +16,8 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
@ -29,27 +27,18 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel;
|
||||
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.ParameterMappingException;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.ParametersMissingException;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.Link;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.reactive.AbstractWebFluxEndpointHandlerMapping;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.reactive.HandlerMapping;
|
||||
import org.springframework.web.reactive.result.method.RequestMappingInfoHandlerMapping;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
@ -58,45 +47,33 @@ import org.springframework.web.server.ServerWebExchange;
|
||||
* Cloud Foundry specific URLs over HTTP using Spring WebFlux.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class CloudFoundryWebFluxEndpointHandlerMapping
|
||||
extends AbstractWebFluxEndpointHandlerMapping {
|
||||
|
||||
private final Method handleRead = ReflectionUtils
|
||||
.findMethod(ReadOperationHandler.class, "handle", ServerWebExchange.class);
|
||||
private final CloudFoundrySecurityInterceptor securityInterceptor;
|
||||
|
||||
private final Method handleWrite = ReflectionUtils.findMethod(
|
||||
WriteOperationHandler.class, "handle", ServerWebExchange.class, Map.class);
|
||||
private final EndpointLinksResolver linksResolver = new EndpointLinksResolver();
|
||||
|
||||
private final Method links = ReflectionUtils.findMethod(getClass(), "links",
|
||||
ServerWebExchange.class);
|
||||
|
||||
private final EndpointLinksResolver endpointLinksResolver = new EndpointLinksResolver();
|
||||
|
||||
private final ReactiveCloudFoundrySecurityInterceptor securityInterceptor;
|
||||
|
||||
@Override
|
||||
protected Method getLinks() {
|
||||
return this.links;
|
||||
CloudFoundryWebFluxEndpointHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<ExposableWebEndpoint> endpoints,
|
||||
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration,
|
||||
CloudFoundrySecurityInterceptor securityInterceptor) {
|
||||
super(endpointMapping, endpoints, endpointMediaTypes, corsConfiguration);
|
||||
this.securityInterceptor = securityInterceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerMappingForOperation(WebOperation operation) {
|
||||
OperationType operationType = operation.getType();
|
||||
OperationInvoker operationInvoker = operation.getInvoker();
|
||||
if (operation.isBlocking()) {
|
||||
operationInvoker = new ElasticSchedulerOperationInvoker(operationInvoker);
|
||||
}
|
||||
Object handler = (operationType == OperationType.WRITE
|
||||
? new WriteOperationHandler(operationInvoker, operation.getId())
|
||||
: new ReadOperationHandler(operationInvoker, operation.getId()));
|
||||
Method method = (operationType == OperationType.WRITE ? this.handleWrite
|
||||
: this.handleRead);
|
||||
registerMapping(createRequestMappingInfo(operation), handler, method);
|
||||
protected ReactiveWebOperation wrapReactiveWebOperation(ExposableWebEndpoint endpoint,
|
||||
WebOperation operation, ReactiveWebOperation reactiveWebOperation) {
|
||||
return new SecureReactiveWebOperation(reactiveWebOperation,
|
||||
this.securityInterceptor, endpoint.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ResponseBody
|
||||
private Publisher<ResponseEntity<Object>> links(ServerWebExchange exchange) {
|
||||
protected Publisher<ResponseEntity<Object>> links(ServerWebExchange exchange) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
return this.securityInterceptor.preHandle(exchange, "")
|
||||
.map((securityResponse) -> {
|
||||
@ -105,7 +82,7 @@ class CloudFoundryWebFluxEndpointHandlerMapping
|
||||
}
|
||||
AccessLevel accessLevel = exchange
|
||||
.getAttribute(AccessLevel.REQUEST_ATTRIBUTE);
|
||||
Map<String, Link> links = this.endpointLinksResolver
|
||||
Map<String, Link> links = this.linksResolver
|
||||
.resolveLinks(getEndpoints(), request.getURI().toString());
|
||||
return new ResponseEntity<>(
|
||||
Collections.singletonMap("_links",
|
||||
@ -126,117 +103,37 @@ class CloudFoundryWebFluxEndpointHandlerMapping
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code WebEndpointHandlerMapping} that provides mappings for the
|
||||
* operations of the given {@code webEndpoints}.
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param webEndpoints the web endpoints
|
||||
* @param endpointMediaTypes media types consumed and produced by the endpoints
|
||||
* @param corsConfiguration the CORS configuration for the endpoints
|
||||
* @param securityInterceptor the Security Interceptor
|
||||
* {@link ReactiveWebOperation} wrapper to add security.
|
||||
*/
|
||||
CloudFoundryWebFluxEndpointHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebOperation>> webEndpoints,
|
||||
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration,
|
||||
ReactiveCloudFoundrySecurityInterceptor securityInterceptor) {
|
||||
super(endpointMapping, webEndpoints, endpointMediaTypes, corsConfiguration);
|
||||
this.securityInterceptor = securityInterceptor;
|
||||
}
|
||||
private static class SecureReactiveWebOperation implements ReactiveWebOperation {
|
||||
|
||||
/**
|
||||
* Base class for handlers for endpoint operations.
|
||||
*/
|
||||
abstract class AbstractOperationHandler {
|
||||
private final ReactiveWebOperation delegate;
|
||||
|
||||
private final OperationInvoker operationInvoker;
|
||||
private final CloudFoundrySecurityInterceptor securityInterceptor;
|
||||
|
||||
private final String endpointId;
|
||||
|
||||
private final ReactiveCloudFoundrySecurityInterceptor securityInterceptor;
|
||||
|
||||
AbstractOperationHandler(OperationInvoker operationInvoker, String endpointId,
|
||||
ReactiveCloudFoundrySecurityInterceptor securityInterceptor) {
|
||||
this.operationInvoker = operationInvoker;
|
||||
this.endpointId = endpointId;
|
||||
SecureReactiveWebOperation(ReactiveWebOperation delegate,
|
||||
CloudFoundrySecurityInterceptor securityInterceptor, String endpointId) {
|
||||
this.delegate = delegate;
|
||||
this.securityInterceptor = securityInterceptor;
|
||||
this.endpointId = endpointId;
|
||||
}
|
||||
|
||||
Publisher<ResponseEntity<Object>> doHandle(ServerWebExchange exchange,
|
||||
@Override
|
||||
public Mono<ResponseEntity<Object>> handle(ServerWebExchange exchange,
|
||||
Map<String, String> body) {
|
||||
return this.securityInterceptor.preHandle(exchange, this.endpointId)
|
||||
.flatMap((securityResponse) -> flatMapResponse(exchange, body,
|
||||
securityResponse));
|
||||
}
|
||||
|
||||
private Mono<? extends ResponseEntity<Object>> flatMapResponse(
|
||||
ServerWebExchange exchange, Map<String, String> body,
|
||||
SecurityResponse securityResponse) {
|
||||
private Mono<ResponseEntity<Object>> flatMapResponse(ServerWebExchange exchange,
|
||||
Map<String, String> body, SecurityResponse securityResponse) {
|
||||
if (!securityResponse.getStatus().equals(HttpStatus.OK)) {
|
||||
return Mono.just(new ResponseEntity<>(securityResponse.getStatus()));
|
||||
}
|
||||
Map<String, Object> arguments = new HashMap<>(exchange
|
||||
.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE));
|
||||
if (body != null) {
|
||||
arguments.putAll(body);
|
||||
}
|
||||
exchange.getRequest().getQueryParams().forEach((name, values) -> arguments
|
||||
.put(name, (values.size() == 1 ? values.get(0) : values)));
|
||||
return handleResult((Publisher<?>) this.operationInvoker.invoke(arguments),
|
||||
exchange.getRequest().getMethod());
|
||||
}
|
||||
|
||||
private Mono<ResponseEntity<Object>> handleResult(Publisher<?> result,
|
||||
HttpMethod httpMethod) {
|
||||
return Mono.from(result).map(this::toResponseEntity)
|
||||
.onErrorReturn(ParametersMissingException.class,
|
||||
new ResponseEntity<>(HttpStatus.BAD_REQUEST))
|
||||
.onErrorReturn(ParameterMappingException.class,
|
||||
new ResponseEntity<>(HttpStatus.BAD_REQUEST))
|
||||
.defaultIfEmpty(new ResponseEntity<>(httpMethod == HttpMethod.GET
|
||||
? HttpStatus.NOT_FOUND : HttpStatus.NO_CONTENT));
|
||||
}
|
||||
|
||||
private ResponseEntity<Object> toResponseEntity(Object response) {
|
||||
if (!(response instanceof WebEndpointResponse)) {
|
||||
return new ResponseEntity<>(response, HttpStatus.OK);
|
||||
}
|
||||
WebEndpointResponse<?> webEndpointResponse = (WebEndpointResponse<?>) response;
|
||||
return new ResponseEntity<>(webEndpointResponse.getBody(),
|
||||
HttpStatus.valueOf(webEndpointResponse.getStatus()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A handler for an endpoint write operation.
|
||||
*/
|
||||
final class WriteOperationHandler extends AbstractOperationHandler {
|
||||
|
||||
WriteOperationHandler(OperationInvoker operationInvoker, String endpointId) {
|
||||
super(operationInvoker, endpointId,
|
||||
CloudFoundryWebFluxEndpointHandlerMapping.this.securityInterceptor);
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
public Publisher<ResponseEntity<Object>> handle(ServerWebExchange exchange,
|
||||
@RequestBody(required = false) Map<String, String> body) {
|
||||
return doHandle(exchange, body);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A handler for an endpoint write operation.
|
||||
*/
|
||||
final class ReadOperationHandler extends AbstractOperationHandler {
|
||||
|
||||
ReadOperationHandler(OperationInvoker operationInvoker, String endpointId) {
|
||||
super(operationInvoker, endpointId,
|
||||
CloudFoundryWebFluxEndpointHandlerMapping.this.securityInterceptor);
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
public Publisher<ResponseEntity<Object>> handle(ServerWebExchange exchange) {
|
||||
return doHandle(exchange, null);
|
||||
return this.delegate.handle(exchange, body);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,10 +21,10 @@ import java.util.Collections;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryWebAnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryWebEndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
|
||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||
@ -84,27 +84,27 @@ public class ReactiveCloudFoundryActuatorAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public CloudFoundryWebFluxEndpointHandlerMapping cloudFoundryWebFluxEndpointHandlerMapping(
|
||||
ParameterMapper parameterMapper, EndpointMediaTypes endpointMediaTypes,
|
||||
ParameterValueMapper parameterMapper, EndpointMediaTypes endpointMediaTypes,
|
||||
WebClient.Builder webClientBuilder) {
|
||||
CloudFoundryWebAnnotationEndpointDiscoverer endpointDiscoverer = new CloudFoundryWebAnnotationEndpointDiscoverer(
|
||||
CloudFoundryWebEndpointDiscoverer endpointDiscoverer = new CloudFoundryWebEndpointDiscoverer(
|
||||
this.applicationContext, parameterMapper, endpointMediaTypes,
|
||||
EndpointPathResolver.useEndpointId(), null, null,
|
||||
CloudFoundryReactiveHealthEndpointWebExtension.class);
|
||||
ReactiveCloudFoundrySecurityInterceptor securityInterceptor = getSecurityInterceptor(
|
||||
EndpointPathResolver.useEndpointId(), Collections.emptyList(),
|
||||
Collections.emptyList());
|
||||
CloudFoundrySecurityInterceptor securityInterceptor = getSecurityInterceptor(
|
||||
webClientBuilder, this.applicationContext.getEnvironment());
|
||||
return new CloudFoundryWebFluxEndpointHandlerMapping(
|
||||
new EndpointMapping("/cloudfoundryapplication"),
|
||||
endpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
|
||||
endpointDiscoverer.getEndpoints(), endpointMediaTypes,
|
||||
getCorsConfiguration(), securityInterceptor);
|
||||
}
|
||||
|
||||
private ReactiveCloudFoundrySecurityInterceptor getSecurityInterceptor(
|
||||
private CloudFoundrySecurityInterceptor getSecurityInterceptor(
|
||||
WebClient.Builder restTemplateBuilder, Environment environment) {
|
||||
ReactiveCloudFoundrySecurityService cloudfoundrySecurityService = getCloudFoundrySecurityService(
|
||||
restTemplateBuilder, environment);
|
||||
ReactiveTokenValidator tokenValidator = new ReactiveTokenValidator(
|
||||
cloudfoundrySecurityService);
|
||||
return new ReactiveCloudFoundrySecurityInterceptor(tokenValidator,
|
||||
return new CloudFoundrySecurityInterceptor(tokenValidator,
|
||||
cloudfoundrySecurityService,
|
||||
environment.getProperty("vcap.application.application_id"));
|
||||
}
|
||||
|
@ -17,12 +17,13 @@
|
||||
package org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryWebAnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryWebEndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
|
||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||
@ -85,18 +86,18 @@ public class CloudFoundryActuatorAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandlerMapping(
|
||||
ParameterMapper parameterMapper, EndpointMediaTypes endpointMediaTypes,
|
||||
ParameterValueMapper parameterMapper, EndpointMediaTypes endpointMediaTypes,
|
||||
RestTemplateBuilder restTemplateBuilder) {
|
||||
CloudFoundryWebAnnotationEndpointDiscoverer endpointDiscoverer = new CloudFoundryWebAnnotationEndpointDiscoverer(
|
||||
CloudFoundryWebEndpointDiscoverer discoverer = new CloudFoundryWebEndpointDiscoverer(
|
||||
this.applicationContext, parameterMapper, endpointMediaTypes,
|
||||
EndpointPathResolver.useEndpointId(), null, null,
|
||||
CloudFoundryHealthEndpointWebExtension.class);
|
||||
EndpointPathResolver.useEndpointId(), Collections.emptyList(),
|
||||
Collections.emptyList());
|
||||
CloudFoundrySecurityInterceptor securityInterceptor = getSecurityInterceptor(
|
||||
restTemplateBuilder, this.applicationContext.getEnvironment());
|
||||
return new CloudFoundryWebEndpointServletHandlerMapping(
|
||||
new EndpointMapping("/cloudfoundryapplication"),
|
||||
endpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
|
||||
getCorsConfiguration(), securityInterceptor);
|
||||
discoverer.getEndpoints(), endpointMediaTypes, getCorsConfiguration(),
|
||||
securityInterceptor);
|
||||
}
|
||||
|
||||
private CloudFoundrySecurityInterceptor getSecurityInterceptor(
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryEndpointFilter;
|
||||
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.HealthEndpointCloudFoundryExtension;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
@ -31,7 +31,7 @@ import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
|
||||
* @author Madhura Bhave
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@EndpointExtension(filter = CloudFoundryEndpointFilter.class, endpoint = HealthEndpoint.class)
|
||||
@HealthEndpointCloudFoundryExtension
|
||||
public class CloudFoundryHealthEndpointWebExtension {
|
||||
|
||||
private final HealthEndpointWebExtension delegate;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -88,12 +88,12 @@ class CloudFoundrySecurityInterceptor {
|
||||
return SecurityResponse.success();
|
||||
}
|
||||
|
||||
private void check(HttpServletRequest request, String path) throws Exception {
|
||||
private void check(HttpServletRequest request, String endpointId) throws Exception {
|
||||
Token token = getToken(request);
|
||||
this.tokenValidator.validate(token);
|
||||
AccessLevel accessLevel = this.cloudFoundrySecurityService
|
||||
.getAccessLevel(token.toString(), this.applicationId);
|
||||
if (!accessLevel.isAccessAllowed(path)) {
|
||||
if (!accessLevel.isAccessAllowed(endpointId)) {
|
||||
throw new CloudFoundryAuthorizationException(Reason.ACCESS_DENIED,
|
||||
"Access denied");
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -16,11 +16,8 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
@ -28,30 +25,19 @@ import java.util.stream.Collectors;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel;
|
||||
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.ParameterMappingException;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.ParametersMissingException;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.Link;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
|
||||
|
||||
/**
|
||||
@ -59,39 +45,33 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi
|
||||
* Cloud Foundry specific URLs over HTTP using Spring MVC.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class CloudFoundryWebEndpointServletHandlerMapping
|
||||
extends AbstractWebMvcEndpointHandlerMapping {
|
||||
|
||||
private final Method handle = ReflectionUtils.findMethod(OperationHandler.class,
|
||||
"handle", HttpServletRequest.class, Map.class);
|
||||
|
||||
private final Method links = ReflectionUtils.findMethod(
|
||||
CloudFoundryWebEndpointServletHandlerMapping.class, "links",
|
||||
HttpServletRequest.class, HttpServletResponse.class);
|
||||
|
||||
private static final Log logger = LogFactory
|
||||
.getLog(CloudFoundryWebEndpointServletHandlerMapping.class);
|
||||
|
||||
private final CloudFoundrySecurityInterceptor securityInterceptor;
|
||||
|
||||
private final EndpointLinksResolver endpointLinksResolver = new EndpointLinksResolver();
|
||||
private final EndpointLinksResolver linksResolver = new EndpointLinksResolver();
|
||||
|
||||
CloudFoundryWebEndpointServletHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebOperation>> webEndpoints,
|
||||
Collection<ExposableWebEndpoint> endpoints,
|
||||
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration,
|
||||
CloudFoundrySecurityInterceptor securityInterceptor) {
|
||||
super(endpointMapping, webEndpoints, endpointMediaTypes, corsConfiguration);
|
||||
super(endpointMapping, endpoints, endpointMediaTypes, corsConfiguration);
|
||||
this.securityInterceptor = securityInterceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Method getLinks() {
|
||||
return this.links;
|
||||
protected ServletWebOperation wrapServletWebOperation(ExposableWebEndpoint endpoint,
|
||||
WebOperation operation, ServletWebOperation servletWebOperation) {
|
||||
return new SecureServletWebOperation(servletWebOperation,
|
||||
this.securityInterceptor, endpoint.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ResponseBody
|
||||
private Map<String, Map<String, Link>> links(HttpServletRequest request,
|
||||
protected Map<String, Map<String, Link>> links(HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
SecurityResponse securityResponse = this.securityInterceptor.preHandle(request,
|
||||
"");
|
||||
@ -100,7 +80,7 @@ class CloudFoundryWebEndpointServletHandlerMapping
|
||||
}
|
||||
AccessLevel accessLevel = (AccessLevel) request
|
||||
.getAttribute(AccessLevel.REQUEST_ATTRIBUTE);
|
||||
Map<String, Link> links = this.endpointLinksResolver.resolveLinks(getEndpoints(),
|
||||
Map<String, Link> links = this.linksResolver.resolveLinks(getEndpoints(),
|
||||
request.getRequestURL().toString());
|
||||
Map<String, Link> filteredLinks = new LinkedHashMap<>();
|
||||
if (accessLevel == null) {
|
||||
@ -120,83 +100,38 @@ class CloudFoundryWebEndpointServletHandlerMapping
|
||||
securityResponse.getMessage());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.debug("Failed to send error response", ex);
|
||||
this.logger.debug("Failed to send error response", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerMappingForOperation(WebOperation operation) {
|
||||
registerMapping(createRequestMappingInfo(operation),
|
||||
new OperationHandler(operation.getInvoker(), operation.getId(),
|
||||
this.securityInterceptor),
|
||||
this.handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler which has the handler method and security interceptor.
|
||||
* {@link ServletWebOperation} wrapper to add security.
|
||||
*/
|
||||
final class OperationHandler {
|
||||
private static class SecureServletWebOperation implements ServletWebOperation {
|
||||
|
||||
private final OperationInvoker operationInvoker;
|
||||
|
||||
private final String endpointId;
|
||||
private final ServletWebOperation delegate;
|
||||
|
||||
private final CloudFoundrySecurityInterceptor securityInterceptor;
|
||||
|
||||
OperationHandler(OperationInvoker operationInvoker, String id,
|
||||
CloudFoundrySecurityInterceptor securityInterceptor) {
|
||||
this.operationInvoker = operationInvoker;
|
||||
this.endpointId = id;
|
||||
private final String endpointId;
|
||||
|
||||
SecureServletWebOperation(ServletWebOperation delegate,
|
||||
CloudFoundrySecurityInterceptor securityInterceptor, String endpointId) {
|
||||
this.delegate = delegate;
|
||||
this.securityInterceptor = securityInterceptor;
|
||||
this.endpointId = endpointId;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ResponseBody
|
||||
public Object handle(HttpServletRequest request,
|
||||
@RequestBody(required = false) Map<String, String> body) {
|
||||
@Override
|
||||
public Object handle(HttpServletRequest request, Map<String, String> body) {
|
||||
SecurityResponse securityResponse = this.securityInterceptor
|
||||
.preHandle(request, this.endpointId);
|
||||
if (!securityResponse.getStatus().equals(HttpStatus.OK)) {
|
||||
return failureResponse(securityResponse);
|
||||
}
|
||||
Map<String, Object> arguments = new HashMap<>((Map<String, String>) request
|
||||
.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE));
|
||||
HttpMethod httpMethod = HttpMethod.valueOf(request.getMethod());
|
||||
if (body != null && HttpMethod.POST == httpMethod) {
|
||||
arguments.putAll(body);
|
||||
}
|
||||
request.getParameterMap().forEach((name, values) -> arguments.put(name,
|
||||
values.length == 1 ? values[0] : Arrays.asList(values)));
|
||||
try {
|
||||
return handleResult(this.operationInvoker.invoke(arguments), httpMethod);
|
||||
}
|
||||
catch (ParametersMissingException | ParameterMappingException ex) {
|
||||
return new ResponseEntity<Void>(HttpStatus.BAD_REQUEST);
|
||||
return new ResponseEntity<Object>(securityResponse.getMessage(),
|
||||
securityResponse.getStatus());
|
||||
}
|
||||
return this.delegate.handle(request, body);
|
||||
}
|
||||
|
||||
private Object failureResponse(SecurityResponse response) {
|
||||
return handleResult(new WebEndpointResponse<>(response.getMessage(),
|
||||
response.getStatus().value()));
|
||||
}
|
||||
|
||||
private Object handleResult(Object result) {
|
||||
return handleResult(result, null);
|
||||
}
|
||||
|
||||
private Object handleResult(Object result, HttpMethod httpMethod) {
|
||||
if (result == null) {
|
||||
return new ResponseEntity<>(httpMethod == HttpMethod.GET
|
||||
? HttpStatus.NOT_FOUND : HttpStatus.NO_CONTENT);
|
||||
}
|
||||
if (!(result instanceof WebEndpointResponse)) {
|
||||
return result;
|
||||
}
|
||||
WebEndpointResponse<?> response = (WebEndpointResponse<?>) result;
|
||||
return new ResponseEntity<Object>(response.getBody(),
|
||||
HttpStatus.valueOf(response.getStatus()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -17,9 +17,9 @@
|
||||
package org.springframework.boot.actuate.autoconfigure.endpoint;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.cache.CachingOperationInvokerAdvisor;
|
||||
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
|
||||
import org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@ -38,8 +38,9 @@ import org.springframework.core.env.Environment;
|
||||
public class EndpointAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public ParameterMapper endpointOperationParameterMapper() {
|
||||
return new ConversionServiceParameterMapper();
|
||||
@ConditionalOnMissingBean
|
||||
public ParameterValueMapper endpointOperationParameterMapper() {
|
||||
return new ConversionServiceParameterValueMapper();
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -19,7 +19,7 @@ package org.springframework.boot.actuate.autoconfigure.endpoint;
|
||||
import java.time.Duration;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.cache.CachingOperationInvokerAdvisor;
|
||||
import org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor;
|
||||
import org.springframework.boot.context.properties.bind.BindResult;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -24,10 +24,8 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointFilter;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.Operation;
|
||||
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.core.env.Environment;
|
||||
@ -37,14 +35,14 @@ import org.springframework.util.Assert;
|
||||
* {@link EndpointFilter} that will filter endpoints based on {@code expose} and
|
||||
* {@code exclude} properties.
|
||||
*
|
||||
* @param <T> The operation type
|
||||
* @param <E> The endpoint type
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class ExposeExcludePropertyEndpointFilter<T extends Operation>
|
||||
implements EndpointFilter<T> {
|
||||
public class ExposeExcludePropertyEndpointFilter<E extends ExposableEndpoint<?>>
|
||||
implements EndpointFilter<E> {
|
||||
|
||||
private final Class<? extends EndpointDiscoverer<T>> discovererType;
|
||||
private final Class<E> endpointType;
|
||||
|
||||
private final Set<String> expose;
|
||||
|
||||
@ -52,25 +50,23 @@ public class ExposeExcludePropertyEndpointFilter<T extends Operation>
|
||||
|
||||
private final Set<String> exposeDefaults;
|
||||
|
||||
public ExposeExcludePropertyEndpointFilter(
|
||||
Class<? extends EndpointDiscoverer<T>> discovererType,
|
||||
public ExposeExcludePropertyEndpointFilter(Class<E> endpointType,
|
||||
Environment environment, String prefix, String... exposeDefaults) {
|
||||
Assert.notNull(discovererType, "Discoverer Type must not be null");
|
||||
Assert.notNull(endpointType, "EndpointType must not be null");
|
||||
Assert.notNull(environment, "Environment must not be null");
|
||||
Assert.hasText(prefix, "Prefix must not be empty");
|
||||
Binder binder = Binder.get(environment);
|
||||
this.discovererType = discovererType;
|
||||
this.endpointType = endpointType;
|
||||
this.expose = bind(binder, prefix + ".expose");
|
||||
this.exclude = bind(binder, prefix + ".exclude");
|
||||
this.exposeDefaults = asSet(Arrays.asList(exposeDefaults));
|
||||
}
|
||||
|
||||
public ExposeExcludePropertyEndpointFilter(
|
||||
Class<? extends EndpointDiscoverer<T>> discovererType,
|
||||
public ExposeExcludePropertyEndpointFilter(Class<E> endpointType,
|
||||
Collection<String> expose, Collection<String> exclude,
|
||||
String... exposeDefaults) {
|
||||
Assert.notNull(discovererType, "Discoverer Type must not be null");
|
||||
this.discovererType = discovererType;
|
||||
Assert.notNull(endpointType, "EndpointType Type must not be null");
|
||||
this.endpointType = endpointType;
|
||||
this.expose = asSet(expose);
|
||||
this.exclude = asSet(exclude);
|
||||
this.exposeDefaults = asSet(Arrays.asList(exposeDefaults));
|
||||
@ -90,30 +86,30 @@ public class ExposeExcludePropertyEndpointFilter<T extends Operation>
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(EndpointInfo<T> info, EndpointDiscoverer<T> discoverer) {
|
||||
if (this.discovererType.isInstance(discoverer)) {
|
||||
return isExposed(info) && !isExcluded(info);
|
||||
public boolean match(E endpoint) {
|
||||
if (this.endpointType.isInstance(endpoint)) {
|
||||
return isExposed(endpoint) && !isExcluded(endpoint);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isExposed(EndpointInfo<T> info) {
|
||||
private boolean isExposed(ExposableEndpoint<?> endpoint) {
|
||||
if (this.expose.isEmpty()) {
|
||||
return this.exposeDefaults.contains("*")
|
||||
|| contains(this.exposeDefaults, info);
|
||||
|| contains(this.exposeDefaults, endpoint);
|
||||
}
|
||||
return this.expose.contains("*") || contains(this.expose, info);
|
||||
return this.expose.contains("*") || contains(this.expose, endpoint);
|
||||
}
|
||||
|
||||
private boolean isExcluded(EndpointInfo<T> info) {
|
||||
private boolean isExcluded(ExposableEndpoint<?> endpoint) {
|
||||
if (this.exclude.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return this.exclude.contains("*") || contains(this.exclude, info);
|
||||
return this.exclude.contains("*") || contains(this.exclude, endpoint);
|
||||
}
|
||||
|
||||
private boolean contains(Set<String> items, EndpointInfo<T> info) {
|
||||
return items.contains(info.getId().toLowerCase());
|
||||
private boolean contains(Set<String> items, ExposableEndpoint<?> endpoint) {
|
||||
return items.contains(endpoint.getId().toLowerCase());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -16,14 +16,12 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.endpoint.jmx;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.management.MBeanServer;
|
||||
import javax.management.MalformedObjectNameException;
|
||||
import javax.management.ObjectName;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.jmx.EndpointMBean;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.EndpointObjectNameFactory;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.ExposableJmxEndpoint;
|
||||
import org.springframework.jmx.support.ObjectNameManager;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
@ -50,15 +48,18 @@ class DefaultEndpointObjectNameFactory implements EndpointObjectNameFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectName generate(EndpointMBean mBean) throws MalformedObjectNameException {
|
||||
String baseObjectName = this.properties.getDomain() + ":type=Endpoint" + ",name="
|
||||
+ StringUtils.capitalize(mBean.getEndpointId());
|
||||
StringBuilder builder = new StringBuilder(baseObjectName);
|
||||
if (this.mBeanServer != null && hasMBean(baseObjectName)) {
|
||||
builder.append(",context=").append(this.contextId);
|
||||
public ObjectName getObjectName(ExposableJmxEndpoint endpoint)
|
||||
throws MalformedObjectNameException {
|
||||
StringBuilder builder = new StringBuilder(this.properties.getDomain());
|
||||
builder.append(":type=Endpoint");
|
||||
builder.append(",name=" + StringUtils.capitalize(endpoint.getId()));
|
||||
String baseName = builder.toString();
|
||||
if (this.mBeanServer != null && hasMBean(baseName)) {
|
||||
builder.append(",context=" + this.contextId);
|
||||
}
|
||||
if (this.properties.isUniqueNames()) {
|
||||
builder.append(",identity=").append(ObjectUtils.getIdentityHexString(mBean));
|
||||
String identity = ObjectUtils.getIdentityHexString(endpoint);
|
||||
builder.append(",identity=" + identity);
|
||||
}
|
||||
builder.append(getStaticNames());
|
||||
return ObjectNameManager.getInstance(builder.toString());
|
||||
@ -74,10 +75,8 @@ class DefaultEndpointObjectNameFactory implements EndpointObjectNameFactory {
|
||||
return "";
|
||||
}
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (Map.Entry<Object, Object> name : this.properties.getStaticNames()
|
||||
.entrySet()) {
|
||||
builder.append(",").append(name.getKey()).append("=").append(name.getValue());
|
||||
}
|
||||
this.properties.getStaticNames()
|
||||
.forEach((name, value) -> builder.append("," + name + "=" + value));
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -17,6 +17,8 @@
|
||||
package org.springframework.boot.actuate.autoconfigure.endpoint.jmx;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.management.MBeanServer;
|
||||
|
||||
@ -26,14 +28,18 @@ import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.ExposeExcludePropertyEndpointFilter;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointFilter;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.EndpointMBeanRegistrar;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.EndpointObjectNameFactory;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.JmxOperation;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxAnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInvokerAdvisor;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.ExposableJmxEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.JacksonJmxOperationResponseMapper;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.JmxEndpointExporter;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.JmxEndpointsSupplier;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.JmxOperationResponseMapper;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxEndpointDiscoverer;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
|
||||
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
|
||||
@ -66,34 +72,37 @@ public class JmxEndpointAutoConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JmxAnnotationEndpointDiscoverer jmxAnnotationEndpointDiscoverer(
|
||||
ParameterMapper parameterMapper,
|
||||
Collection<OperationMethodInvokerAdvisor> invokerAdvisors,
|
||||
Collection<EndpointFilter<JmxOperation>> filters) {
|
||||
return new JmxAnnotationEndpointDiscoverer(this.applicationContext,
|
||||
parameterMapper, invokerAdvisors, filters);
|
||||
@ConditionalOnMissingBean(JmxEndpointsSupplier.class)
|
||||
public JmxEndpointDiscoverer jmxAnnotationEndpointDiscoverer(
|
||||
ParameterValueMapper parameterValueMapper,
|
||||
ObjectProvider<Collection<OperationInvokerAdvisor>> invokerAdvisors,
|
||||
ObjectProvider<Collection<EndpointFilter<ExposableJmxEndpoint>>> filters) {
|
||||
return new JmxEndpointDiscoverer(this.applicationContext, parameterValueMapper,
|
||||
invokerAdvisors.getIfAvailable(Collections::emptyList),
|
||||
filters.getIfAvailable(Collections::emptyList));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnSingleCandidate(MBeanServer.class)
|
||||
public JmxEndpointExporter jmxMBeanExporter(
|
||||
JmxAnnotationEndpointDiscoverer jmxAnnotationEndpointDiscoverer,
|
||||
MBeanServer mBeanServer, JmxAnnotationEndpointDiscoverer endpointDiscoverer,
|
||||
ObjectProvider<ObjectMapper> objectMapper) {
|
||||
public JmxEndpointExporter jmxMBeanExporter(MBeanServer mBeanServer,
|
||||
ObjectProvider<ObjectMapper> objectMapper,
|
||||
JmxEndpointsSupplier jmxEndpointsSupplier) {
|
||||
String contextId = ObjectUtils.getIdentityHexString(this.applicationContext);
|
||||
EndpointObjectNameFactory objectNameFactory = new DefaultEndpointObjectNameFactory(
|
||||
this.properties, mBeanServer,
|
||||
ObjectUtils.getIdentityHexString(this.applicationContext));
|
||||
EndpointMBeanRegistrar registrar = new EndpointMBeanRegistrar(mBeanServer,
|
||||
objectNameFactory);
|
||||
return new JmxEndpointExporter(jmxAnnotationEndpointDiscoverer, registrar,
|
||||
objectMapper.getIfAvailable(ObjectMapper::new));
|
||||
this.properties, mBeanServer, contextId);
|
||||
JmxOperationResponseMapper responseMapper = new JacksonJmxOperationResponseMapper(
|
||||
objectMapper.getIfAvailable());
|
||||
return new JmxEndpointExporter(mBeanServer, objectNameFactory, responseMapper,
|
||||
jmxEndpointsSupplier.getEndpoints());
|
||||
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ExposeExcludePropertyEndpointFilter<JmxOperation> jmxIncludeExcludePropertyEndpointFilter() {
|
||||
return new ExposeExcludePropertyEndpointFilter<>(
|
||||
JmxAnnotationEndpointDiscoverer.class, this.properties.getExpose(),
|
||||
this.properties.getExclude(), "*");
|
||||
public ExposeExcludePropertyEndpointFilter<ExposableJmxEndpoint> jmxIncludeExcludePropertyEndpointFilter() {
|
||||
Set<String> expose = this.properties.getExpose();
|
||||
Set<String> exclude = this.properties.getExclude();
|
||||
return new ExposeExcludePropertyEndpointFilter<>(ExposableJmxEndpoint.class,
|
||||
expose, exclude, "*");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,132 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.autoconfigure.endpoint.jmx;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.management.MBeanServer;
|
||||
import javax.management.ObjectName;
|
||||
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.EndpointMBeanRegistrar;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.JmxEndpointMBeanFactory;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.JmxOperation;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.JmxOperationResponseMapper;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxAnnotationEndpointDiscoverer;
|
||||
|
||||
/**
|
||||
* Exports all available {@link Endpoint} to a configurable {@link MBeanServer}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class JmxEndpointExporter implements InitializingBean, DisposableBean {
|
||||
|
||||
private final JmxAnnotationEndpointDiscoverer endpointDiscoverer;
|
||||
|
||||
private final EndpointMBeanRegistrar endpointMBeanRegistrar;
|
||||
|
||||
private final JmxEndpointMBeanFactory mBeanFactory;
|
||||
|
||||
private Collection<ObjectName> registeredObjectNames;
|
||||
|
||||
JmxEndpointExporter(JmxAnnotationEndpointDiscoverer endpointDiscoverer,
|
||||
EndpointMBeanRegistrar endpointMBeanRegistrar, ObjectMapper objectMapper) {
|
||||
this.endpointDiscoverer = endpointDiscoverer;
|
||||
this.endpointMBeanRegistrar = endpointMBeanRegistrar;
|
||||
DataConverter dataConverter = new DataConverter(objectMapper);
|
||||
this.mBeanFactory = new JmxEndpointMBeanFactory(dataConverter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
this.registeredObjectNames = registerEndpointMBeans();
|
||||
}
|
||||
|
||||
private Collection<ObjectName> registerEndpointMBeans() {
|
||||
Collection<EndpointInfo<JmxOperation>> endpoints = this.endpointDiscoverer
|
||||
.discoverEndpoints();
|
||||
return this.mBeanFactory.createMBeans(endpoints).stream()
|
||||
.map(this.endpointMBeanRegistrar::registerEndpointMBean)
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
unregisterEndpointMBeans(this.registeredObjectNames);
|
||||
}
|
||||
|
||||
private void unregisterEndpointMBeans(Collection<ObjectName> objectNames) {
|
||||
objectNames.forEach(this.endpointMBeanRegistrar::unregisterEndpointMbean);
|
||||
|
||||
}
|
||||
|
||||
static class DataConverter implements JmxOperationResponseMapper {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private final JavaType listObject;
|
||||
|
||||
private final JavaType mapStringObject;
|
||||
|
||||
DataConverter(ObjectMapper objectMapper) {
|
||||
this.objectMapper = (objectMapper == null ? new ObjectMapper()
|
||||
: objectMapper);
|
||||
this.listObject = this.objectMapper.getTypeFactory()
|
||||
.constructParametricType(List.class, Object.class);
|
||||
this.mapStringObject = this.objectMapper.getTypeFactory()
|
||||
.constructParametricType(Map.class, String.class, Object.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object mapResponse(Object response) {
|
||||
if (response == null) {
|
||||
return null;
|
||||
}
|
||||
if (response instanceof String) {
|
||||
return response;
|
||||
}
|
||||
if (response.getClass().isArray() || response instanceof Collection) {
|
||||
return this.objectMapper.convertValue(response, this.listObject);
|
||||
}
|
||||
return this.objectMapper.convertValue(response, this.mapStringObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> mapResponseType(Class<?> responseType) {
|
||||
if (responseType.equals(String.class)) {
|
||||
return String.class;
|
||||
}
|
||||
if (responseType.isArray()
|
||||
|| Collection.class.isAssignableFrom(responseType)) {
|
||||
return List.class;
|
||||
}
|
||||
return Map.class;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -16,13 +16,12 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.endpoint.web;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@ -35,33 +34,28 @@ public class DefaultEndpointPathProvider implements EndpointPathProvider {
|
||||
|
||||
private final String basePath;
|
||||
|
||||
private final EndpointDiscoverer<WebOperation> endpointDiscoverer;
|
||||
private final Collection<ExposableWebEndpoint> endpoints;
|
||||
|
||||
public DefaultEndpointPathProvider(
|
||||
EndpointDiscoverer<WebOperation> endpointDiscoverer,
|
||||
WebEndpointProperties webEndpointProperties) {
|
||||
this.endpointDiscoverer = endpointDiscoverer;
|
||||
public DefaultEndpointPathProvider(WebEndpointProperties webEndpointProperties,
|
||||
Collection<? extends ExposableWebEndpoint> endpoints) {
|
||||
this.basePath = webEndpointProperties.getBasePath();
|
||||
this.endpoints = Collections.unmodifiableCollection(endpoints);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getPaths() {
|
||||
return getEndpoints().map(this::getPath).collect(Collectors.toList());
|
||||
return this.endpoints.stream().map(this::getPath).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath(String id) {
|
||||
Assert.notNull(id, "ID must not be null");
|
||||
return getEndpoints().filter((info) -> id.equals(info.getId())).findFirst()
|
||||
.map(this::getPath).orElse(null);
|
||||
return this.endpoints.stream().filter((info) -> id.equals(info.getId()))
|
||||
.findFirst().map(this::getPath).orElse(null);
|
||||
}
|
||||
|
||||
private Stream<EndpointInfo<WebOperation>> getEndpoints() {
|
||||
return this.endpointDiscoverer.discoverEndpoints().stream();
|
||||
}
|
||||
|
||||
private String getPath(EndpointInfo<WebOperation> endpointInfo) {
|
||||
return this.basePath + "/" + endpointInfo.getId();
|
||||
private String getPath(ExposableWebEndpoint endpoint) {
|
||||
return this.basePath + "/" + endpoint.getId();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -18,20 +18,23 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.ExposeExcludePropertyEndpointFilter;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointFilter;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInvokerAdvisor;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
@ -76,17 +79,6 @@ public class WebEndpointAutoConfiguration {
|
||||
return new DefaultEndpointPathResolver(this.properties.getPathMapping());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public WebAnnotationEndpointDiscoverer webAnnotationEndpointDiscoverer(
|
||||
ParameterMapper parameterMapper, EndpointPathResolver endpointPathResolver,
|
||||
Collection<OperationMethodInvokerAdvisor> invokerAdvisors,
|
||||
Collection<EndpointFilter<WebOperation>> filters) {
|
||||
return new WebAnnotationEndpointDiscoverer(this.applicationContext,
|
||||
parameterMapper, endpointMediaTypes(), endpointPathResolver,
|
||||
invokerAdvisors, filters);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public EndpointMediaTypes endpointMediaTypes() {
|
||||
@ -94,18 +86,34 @@ public class WebEndpointAutoConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public EndpointPathProvider endpointPathProvider(
|
||||
EndpointDiscoverer<WebOperation> endpointDiscoverer,
|
||||
WebEndpointProperties webEndpointProperties) {
|
||||
return new DefaultEndpointPathProvider(endpointDiscoverer, webEndpointProperties);
|
||||
@ConditionalOnMissingBean(WebEndpointsSupplier.class)
|
||||
public WebEndpointDiscoverer webEndpointDiscoverer(
|
||||
ParameterValueMapper parameterValueMapper,
|
||||
EndpointMediaTypes endpointMediaTypes,
|
||||
EndpointPathResolver endpointPathResolver,
|
||||
ObjectProvider<Collection<OperationInvokerAdvisor>> invokerAdvisors,
|
||||
ObjectProvider<Collection<EndpointFilter<ExposableWebEndpoint>>> filters) {
|
||||
return new WebEndpointDiscoverer(this.applicationContext, parameterValueMapper,
|
||||
endpointMediaTypes, endpointPathResolver,
|
||||
invokerAdvisors.getIfAvailable(Collections::emptyList),
|
||||
filters.getIfAvailable(Collections::emptyList));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ExposeExcludePropertyEndpointFilter<WebOperation> webIncludeExcludePropertyEndpointFilter() {
|
||||
return new ExposeExcludePropertyEndpointFilter<>(
|
||||
WebAnnotationEndpointDiscoverer.class, this.properties.getExpose(),
|
||||
this.properties.getExclude(), "info", "health");
|
||||
@ConditionalOnMissingBean
|
||||
public EndpointPathProvider endpointPathProvider(
|
||||
WebEndpointsSupplier webEndpointsSupplier,
|
||||
WebEndpointProperties webEndpointProperties) {
|
||||
return new DefaultEndpointPathProvider(webEndpointProperties,
|
||||
webEndpointsSupplier.getEndpoints());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ExposeExcludePropertyEndpointFilter<ExposableWebEndpoint> webIncludeExcludePropertyEndpointFilter() {
|
||||
Set<String> expose = this.properties.getExpose();
|
||||
Set<String> exclude = this.properties.getExclude();
|
||||
return new ExposeExcludePropertyEndpointFilter<>(ExposableWebEndpoint.class,
|
||||
expose, exclude, "info", "health");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.endpoint.web.jersey;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.glassfish.jersey.server.ResourceConfig;
|
||||
@ -24,7 +26,8 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointPr
|
||||
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier;
|
||||
import org.springframework.boot.actuate.endpoint.web.jersey.JerseyEndpointResourceFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
@ -45,19 +48,25 @@ import org.springframework.context.annotation.Configuration;
|
||||
@Configuration
|
||||
@ConditionalOnWebApplication(type = Type.SERVLET)
|
||||
@ConditionalOnClass(ResourceConfig.class)
|
||||
@ConditionalOnBean({ ResourceConfig.class, WebAnnotationEndpointDiscoverer.class })
|
||||
@ConditionalOnBean({ ResourceConfig.class, WebEndpointsSupplier.class })
|
||||
@ConditionalOnMissingBean(type = "org.springframework.web.servlet.DispatcherServlet")
|
||||
class JerseyWebEndpointManagementContextConfiguration {
|
||||
|
||||
@Bean
|
||||
public ResourceConfigCustomizer webEndpointRegistrar(
|
||||
WebAnnotationEndpointDiscoverer endpointDiscoverer,
|
||||
WebEndpointsSupplier webEndpointsSupplier,
|
||||
EndpointMediaTypes endpointMediaTypes,
|
||||
WebEndpointProperties webEndpointProperties) {
|
||||
return (resourceConfig) -> resourceConfig.registerResources(
|
||||
new HashSet<>(new JerseyEndpointResourceFactory().createEndpointResources(
|
||||
new EndpointMapping(webEndpointProperties.getBasePath()),
|
||||
endpointDiscoverer.discoverEndpoints(), endpointMediaTypes)));
|
||||
return (resourceConfig) -> {
|
||||
JerseyEndpointResourceFactory resourceFactory = new JerseyEndpointResourceFactory();
|
||||
String basePath = webEndpointProperties.getBasePath();
|
||||
EndpointMapping endpointMapping = new EndpointMapping(basePath);
|
||||
Collection<ExposableWebEndpoint> endpoints = Collections
|
||||
.unmodifiableCollection(webEndpointsSupplier.getEndpoints());
|
||||
resourceConfig.registerResources(
|
||||
new HashSet<>(resourceFactory.createEndpointResources(endpointMapping,
|
||||
endpoints, endpointMediaTypes)));
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointPr
|
||||
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier;
|
||||
import org.springframework.boot.actuate.endpoint.web.reactive.WebFluxEndpointHandlerMapping;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
@ -44,19 +44,20 @@ import org.springframework.web.reactive.DispatcherHandler;
|
||||
@ManagementContextConfiguration
|
||||
@ConditionalOnWebApplication(type = Type.REACTIVE)
|
||||
@ConditionalOnClass({ DispatcherHandler.class, HttpHandler.class })
|
||||
@ConditionalOnBean(WebAnnotationEndpointDiscoverer.class)
|
||||
@ConditionalOnBean(WebEndpointsSupplier.class)
|
||||
@EnableConfigurationProperties(CorsEndpointProperties.class)
|
||||
public class WebFluxEndpointManagementContextConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public WebFluxEndpointHandlerMapping webEndpointReactiveHandlerMapping(
|
||||
WebAnnotationEndpointDiscoverer endpointDiscoverer,
|
||||
WebEndpointsSupplier webEndpointsSupplier,
|
||||
EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,
|
||||
WebEndpointProperties webEndpointProperties) {
|
||||
return new WebFluxEndpointHandlerMapping(
|
||||
new EndpointMapping(webEndpointProperties.getBasePath()),
|
||||
endpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
|
||||
EndpointMapping endpointMapping = new EndpointMapping(
|
||||
webEndpointProperties.getBasePath());
|
||||
return new WebFluxEndpointHandlerMapping(endpointMapping,
|
||||
webEndpointsSupplier.getEndpoints(), endpointMediaTypes,
|
||||
corsProperties.toCorsConfiguration());
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -21,7 +21,7 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointPr
|
||||
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier;
|
||||
import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
@ -40,25 +40,24 @@ import org.springframework.web.servlet.DispatcherServlet;
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
|
||||
@ManagementContextConfiguration
|
||||
@ConditionalOnWebApplication(type = Type.SERVLET)
|
||||
@ConditionalOnClass(DispatcherServlet.class)
|
||||
@ConditionalOnBean({ DispatcherServlet.class, WebAnnotationEndpointDiscoverer.class })
|
||||
@ConditionalOnBean({ DispatcherServlet.class, WebEndpointsSupplier.class })
|
||||
@EnableConfigurationProperties(CorsEndpointProperties.class)
|
||||
public class WebMvcEndpointManagementContextConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(
|
||||
WebAnnotationEndpointDiscoverer endpointDiscoverer,
|
||||
WebEndpointsSupplier webEndpointsSupplier,
|
||||
EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,
|
||||
WebEndpointProperties webEndpointProperties) {
|
||||
WebMvcEndpointHandlerMapping handlerMapping = new WebMvcEndpointHandlerMapping(
|
||||
new EndpointMapping(webEndpointProperties.getBasePath()),
|
||||
endpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
|
||||
EndpointMapping endpointMapping = new EndpointMapping(
|
||||
webEndpointProperties.getBasePath());
|
||||
return new WebMvcEndpointHandlerMapping(endpointMapping,
|
||||
webEndpointsSupplier.getEndpoints(), endpointMediaTypes,
|
||||
corsProperties.toCorsConfiguration());
|
||||
return handlerMapping;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -16,22 +16,13 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.cloudfoundry;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointFilter;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInvokerAdvisor;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.DiscoveredEndpoint;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link CloudFoundryEndpointFilter}.
|
||||
@ -40,38 +31,22 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*/
|
||||
public class CloudFoundryEndpointFilterTests {
|
||||
|
||||
private CloudFoundryEndpointFilter filter;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.filter = new CloudFoundryEndpointFilter();
|
||||
}
|
||||
private CloudFoundryEndpointFilter filter = new CloudFoundryEndpointFilter();
|
||||
|
||||
@Test
|
||||
public void matchIfDiscovererCloudFoundryShouldReturnFalse() {
|
||||
CloudFoundryWebAnnotationEndpointDiscoverer discoverer = Mockito
|
||||
.mock(CloudFoundryWebAnnotationEndpointDiscoverer.class);
|
||||
assertThat(this.filter.match(null, discoverer)).isTrue();
|
||||
DiscoveredEndpoint<?> endpoint = mock(DiscoveredEndpoint.class);
|
||||
given(endpoint.wasDiscoveredBy(CloudFoundryWebEndpointDiscoverer.class))
|
||||
.willReturn(true);
|
||||
assertThat(this.filter.match(endpoint)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchIfDiscovererNotCloudFoundryShouldReturnFalse() {
|
||||
WebAnnotationEndpointDiscoverer discoverer = Mockito
|
||||
.mock(WebAnnotationEndpointDiscoverer.class);
|
||||
assertThat(this.filter.match(null, discoverer)).isFalse();
|
||||
}
|
||||
|
||||
static class TestEndpointDiscoverer extends WebAnnotationEndpointDiscoverer {
|
||||
|
||||
TestEndpointDiscoverer(ApplicationContext applicationContext,
|
||||
ParameterMapper parameterMapper, EndpointMediaTypes endpointMediaTypes,
|
||||
EndpointPathResolver endpointPathResolver,
|
||||
Collection<? extends OperationMethodInvokerAdvisor> invokerAdvisors,
|
||||
Collection<? extends EndpointFilter<WebOperation>> filters) {
|
||||
super(applicationContext, parameterMapper, endpointMediaTypes,
|
||||
endpointPathResolver, invokerAdvisors, filters);
|
||||
}
|
||||
|
||||
DiscoveredEndpoint<?> endpoint = mock(DiscoveredEndpoint.class);
|
||||
given(endpoint.wasDiscoveredBy(CloudFoundryWebEndpointDiscoverer.class))
|
||||
.willReturn(false);
|
||||
assertThat(this.filter.match(endpoint)).isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -23,59 +23,66 @@ import java.util.function.Function;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.cache.CachingOperationInvokerAdvisor;
|
||||
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper;
|
||||
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;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
|
||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link CloudFoundryWebAnnotationEndpointDiscoverer}.
|
||||
* Tests for {@link CloudFoundryWebEndpointDiscoverer}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class CloudFoundryWebAnnotationEndpointDiscovererTests {
|
||||
public class CloudFoundryWebEndpointDiscovererTests {
|
||||
|
||||
@Test
|
||||
public void discovererShouldAddSuppliedExtensionForHealthEndpoint() {
|
||||
load(TestConfiguration.class, (endpointDiscoverer) -> {
|
||||
Collection<EndpointInfo<WebOperation>> endpoints = endpointDiscoverer
|
||||
.discoverEndpoints();
|
||||
public void getEndpointsShouldAddCloudFoundryHealthExtension() {
|
||||
load(TestConfiguration.class, (discoverer) -> {
|
||||
Collection<ExposableWebEndpoint> endpoints = discoverer.getEndpoints();
|
||||
assertThat(endpoints.size()).isEqualTo(2);
|
||||
for (ExposableWebEndpoint endpoint : endpoints) {
|
||||
if (endpoint.getId().equals("health")) {
|
||||
WebOperation operation = endpoint.getOperations().iterator().next();
|
||||
assertThat(operation.invoke(Collections.emptyMap())).isEqualTo("cf");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void load(Class<?> configuration,
|
||||
Consumer<CloudFoundryWebAnnotationEndpointDiscoverer> consumer) {
|
||||
Consumer<CloudFoundryWebEndpointDiscoverer> consumer) {
|
||||
this.load((id) -> null, (id) -> id, configuration, consumer);
|
||||
}
|
||||
|
||||
private void load(Function<String, Long> timeToLive,
|
||||
EndpointPathResolver endpointPathResolver, Class<?> configuration,
|
||||
Consumer<CloudFoundryWebAnnotationEndpointDiscoverer> consumer) {
|
||||
Consumer<CloudFoundryWebEndpointDiscoverer> consumer) {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
configuration);
|
||||
try {
|
||||
ConversionServiceParameterMapper parameterMapper = new ConversionServiceParameterMapper(
|
||||
ConversionServiceParameterValueMapper parameterMapper = new ConversionServiceParameterValueMapper(
|
||||
DefaultConversionService.getSharedInstance());
|
||||
EndpointMediaTypes mediaTypes = new EndpointMediaTypes(
|
||||
Collections.singletonList("application/json"),
|
||||
Collections.singletonList("application/json"));
|
||||
CloudFoundryWebAnnotationEndpointDiscoverer discoverer = new CloudFoundryWebAnnotationEndpointDiscoverer(
|
||||
CloudFoundryWebEndpointDiscoverer discoverer = new CloudFoundryWebEndpointDiscoverer(
|
||||
context, parameterMapper, mediaTypes, endpointPathResolver,
|
||||
Collections.singleton(new CachingOperationInvokerAdvisor(timeToLive)),
|
||||
null, HealthWebEndpointExtension.class);
|
||||
Collections.emptyList());
|
||||
consumer.accept(discoverer);
|
||||
}
|
||||
finally {
|
||||
@ -91,34 +98,24 @@ public class CloudFoundryWebAnnotationEndpointDiscovererTests {
|
||||
return new TestEndpoint();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TestEndpointWebExtension testEndpointWebExtension() {
|
||||
return new TestEndpointWebExtension();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HealthEndpoint healthEndpoint() {
|
||||
return new HealthEndpoint(null);
|
||||
return new HealthEndpoint(mock(HealthIndicator.class));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TestWebEndpointExtension testEndpointExtension() {
|
||||
return new TestWebEndpointExtension();
|
||||
public HealthEndpointWebExtension healthEndpointWebExtension() {
|
||||
return new HealthEndpointWebExtension();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HealthWebEndpointExtension healthEndpointExtension() {
|
||||
return new HealthWebEndpointExtension();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OtherHealthWebEndpointExtension otherHealthEndpointExtension() {
|
||||
return new OtherHealthWebEndpointExtension();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EndpointWebExtension(endpoint = TestEndpoint.class)
|
||||
static class TestWebEndpointExtension {
|
||||
|
||||
@ReadOperation
|
||||
public Object getAll() {
|
||||
return null;
|
||||
public TestHealthEndpointCloudFoundryExtension testHealthEndpointCloudFoundryExtension() {
|
||||
return new TestHealthEndpointCloudFoundryExtension();
|
||||
}
|
||||
|
||||
}
|
||||
@ -133,8 +130,8 @@ public class CloudFoundryWebAnnotationEndpointDiscovererTests {
|
||||
|
||||
}
|
||||
|
||||
@EndpointWebExtension(endpoint = HealthEndpoint.class)
|
||||
static class HealthWebEndpointExtension {
|
||||
@EndpointWebExtension(endpoint = TestEndpoint.class)
|
||||
static class TestEndpointWebExtension {
|
||||
|
||||
@ReadOperation
|
||||
public Object getAll() {
|
||||
@ -144,7 +141,7 @@ public class CloudFoundryWebAnnotationEndpointDiscovererTests {
|
||||
}
|
||||
|
||||
@EndpointWebExtension(endpoint = HealthEndpoint.class)
|
||||
static class OtherHealthWebEndpointExtension {
|
||||
static class HealthEndpointWebExtension {
|
||||
|
||||
@ReadOperation
|
||||
public Object getAll() {
|
||||
@ -153,4 +150,14 @@ public class CloudFoundryWebAnnotationEndpointDiscovererTests {
|
||||
|
||||
}
|
||||
|
||||
@HealthEndpointCloudFoundryExtension
|
||||
static class TestHealthEndpointCloudFoundryExtension {
|
||||
|
||||
@ReadOperation
|
||||
public Object getAll() {
|
||||
return "cf";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -28,17 +28,15 @@ import reactor.core.publisher.Mono;
|
||||
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel;
|
||||
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException;
|
||||
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException.Reason;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
|
||||
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
|
||||
@ -205,9 +203,9 @@ public class CloudFoundryWebFluxEndpointIntegrationTests {
|
||||
private int port;
|
||||
|
||||
@Bean
|
||||
public ReactiveCloudFoundrySecurityInterceptor interceptor() {
|
||||
return new ReactiveCloudFoundrySecurityInterceptor(tokenValidator,
|
||||
securityService, "app-id");
|
||||
public CloudFoundrySecurityInterceptor interceptor() {
|
||||
return new CloudFoundrySecurityInterceptor(tokenValidator, securityService,
|
||||
"app-id");
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ -218,27 +216,27 @@ public class CloudFoundryWebFluxEndpointIntegrationTests {
|
||||
|
||||
@Bean
|
||||
public CloudFoundryWebFluxEndpointHandlerMapping cloudFoundryWebEndpointServletHandlerMapping(
|
||||
EndpointDiscoverer<WebOperation> webEndpointDiscoverer,
|
||||
WebEndpointDiscoverer webEndpointDiscoverer,
|
||||
EndpointMediaTypes endpointMediaTypes,
|
||||
ReactiveCloudFoundrySecurityInterceptor interceptor) {
|
||||
CloudFoundrySecurityInterceptor interceptor) {
|
||||
CorsConfiguration corsConfiguration = new CorsConfiguration();
|
||||
corsConfiguration.setAllowedOrigins(Arrays.asList("http://example.com"));
|
||||
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST"));
|
||||
return new CloudFoundryWebFluxEndpointHandlerMapping(
|
||||
new EndpointMapping("/cfApplication"),
|
||||
webEndpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
|
||||
webEndpointDiscoverer.getEndpoints(), endpointMediaTypes,
|
||||
corsConfiguration, interceptor);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebAnnotationEndpointDiscoverer webEndpointDiscoverer(
|
||||
public WebEndpointDiscoverer webEndpointDiscoverer(
|
||||
ApplicationContext applicationContext,
|
||||
EndpointMediaTypes endpointMediaTypes) {
|
||||
ParameterMapper parameterMapper = new ConversionServiceParameterMapper(
|
||||
ParameterValueMapper parameterMapper = new ConversionServiceParameterValueMapper(
|
||||
DefaultConversionService.getSharedInstance());
|
||||
return new WebAnnotationEndpointDiscoverer(applicationContext,
|
||||
parameterMapper, endpointMediaTypes,
|
||||
EndpointPathResolver.useEndpointId(), null, null);
|
||||
return new WebEndpointDiscoverer(applicationContext, parameterMapper,
|
||||
endpointMediaTypes, EndpointPathResolver.useEndpointId(),
|
||||
Collections.emptyList(), Collections.emptyList());
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
@ -29,11 +29,11 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfi
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.ReflectiveOperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
|
||||
@ -199,9 +199,8 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests {
|
||||
this.context.register(TestConfiguration.class);
|
||||
this.context.refresh();
|
||||
CloudFoundryWebFluxEndpointHandlerMapping handlerMapping = getHandlerMapping();
|
||||
List<EndpointInfo<WebOperation>> endpoints = (List<EndpointInfo<WebOperation>>) handlerMapping
|
||||
.getEndpoints();
|
||||
List<String> endpointIds = endpoints.stream().map(EndpointInfo::getId)
|
||||
Collection<ExposableWebEndpoint> endpoints = handlerMapping.getEndpoints();
|
||||
List<String> endpointIds = endpoints.stream().map(ExposableEndpoint::getId)
|
||||
.collect(Collectors.toList());
|
||||
assertThat(endpointIds).contains("test");
|
||||
}
|
||||
@ -212,9 +211,8 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests {
|
||||
this.context.register(TestConfiguration.class);
|
||||
this.context.refresh();
|
||||
CloudFoundryWebFluxEndpointHandlerMapping handlerMapping = getHandlerMapping();
|
||||
List<EndpointInfo<WebOperation>> endpoints = (List<EndpointInfo<WebOperation>>) handlerMapping
|
||||
.getEndpoints();
|
||||
EndpointInfo<WebOperation> endpoint = endpoints.stream()
|
||||
Collection<ExposableWebEndpoint> endpoints = handlerMapping.getEndpoints();
|
||||
ExposableWebEndpoint endpoint = endpoints.stream()
|
||||
.filter((candidate) -> "test".equals(candidate.getId())).findFirst()
|
||||
.get();
|
||||
assertThat(endpoint.getOperations()).hasSize(1);
|
||||
@ -226,12 +224,10 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests {
|
||||
public void healthEndpointInvokerShouldBeCloudFoundryWebExtension() {
|
||||
setupContextWithCloudEnabled();
|
||||
this.context.refresh();
|
||||
Collection<EndpointInfo<WebOperation>> endpoints = getHandlerMapping()
|
||||
.getEndpoints();
|
||||
EndpointInfo<WebOperation> endpointInfo = endpoints.iterator().next();
|
||||
WebOperation webOperation = endpointInfo.getOperations().iterator().next();
|
||||
ReflectiveOperationInvoker invoker = (ReflectiveOperationInvoker) webOperation
|
||||
.getInvoker();
|
||||
Collection<ExposableWebEndpoint> endpoints = getHandlerMapping().getEndpoints();
|
||||
ExposableWebEndpoint endpoint = endpoints.iterator().next();
|
||||
WebOperation webOperation = endpoint.getOperations().iterator().next();
|
||||
Object invoker = ReflectionTestUtils.getField(webOperation, "invoker");
|
||||
assertThat(ReflectionTestUtils.getField(invoker, "target"))
|
||||
.isInstanceOf(CloudFoundryReactiveHealthEndpointWebExtension.class);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -37,7 +37,7 @@ import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
|
||||
/**
|
||||
* Tests for {@link ReactiveCloudFoundrySecurityInterceptor}.
|
||||
* Tests for {@link CloudFoundrySecurityInterceptor}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
@ -49,12 +49,12 @@ public class ReactiveCloudFoundrySecurityInterceptorTests {
|
||||
@Mock
|
||||
private ReactiveCloudFoundrySecurityService securityService;
|
||||
|
||||
private ReactiveCloudFoundrySecurityInterceptor interceptor;
|
||||
private CloudFoundrySecurityInterceptor interceptor;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
this.interceptor = new ReactiveCloudFoundrySecurityInterceptor(
|
||||
this.interceptor = new CloudFoundrySecurityInterceptor(
|
||||
this.tokenValidator, this.securityService, "my-app-id");
|
||||
}
|
||||
|
||||
@ -90,7 +90,7 @@ public class ReactiveCloudFoundrySecurityInterceptorTests {
|
||||
|
||||
@Test
|
||||
public void preHandleWhenApplicationIdIsNullShouldReturnError() {
|
||||
this.interceptor = new ReactiveCloudFoundrySecurityInterceptor(
|
||||
this.interceptor = new CloudFoundrySecurityInterceptor(
|
||||
this.tokenValidator, this.securityService, null);
|
||||
MockServerWebExchange request = MockServerWebExchange
|
||||
.from(MockServerHttpRequest.get("/a")
|
||||
@ -105,7 +105,7 @@ public class ReactiveCloudFoundrySecurityInterceptorTests {
|
||||
|
||||
@Test
|
||||
public void preHandleWhenCloudFoundrySecurityServiceIsNullShouldReturnError() {
|
||||
this.interceptor = new ReactiveCloudFoundrySecurityInterceptor(
|
||||
this.interceptor = new CloudFoundrySecurityInterceptor(
|
||||
this.tokenValidator, null, "my-app-id");
|
||||
MockServerWebExchange request = MockServerWebExchange.from(MockServerHttpRequest
|
||||
.get("/a").header(HttpHeaders.AUTHORIZATION, mockAccessToken()).build());
|
||||
|
@ -18,7 +18,6 @@ package org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
@ -29,11 +28,10 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAu
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.ReflectiveOperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
|
||||
@ -43,6 +41,7 @@ import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfigu
|
||||
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.test.util.TestPropertyValues;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@ -99,8 +98,9 @@ public class CloudFoundryActuatorAutoConfigurationTests {
|
||||
@Test
|
||||
public void cloudFoundryPlatformActive() {
|
||||
CloudFoundryWebEndpointServletHandlerMapping handlerMapping = getHandlerMapping();
|
||||
assertThat(handlerMapping.getEndpointMapping().getPath())
|
||||
.isEqualTo("/cloudfoundryapplication");
|
||||
EndpointMapping endpointMapping = (EndpointMapping) ReflectionTestUtils
|
||||
.getField(handlerMapping, "endpointMapping");
|
||||
assertThat(endpointMapping.getPath()).isEqualTo("/cloudfoundryapplication");
|
||||
CorsConfiguration corsConfiguration = (CorsConfiguration) ReflectionTestUtils
|
||||
.getField(handlerMapping, "corsConfiguration");
|
||||
assertThat(corsConfiguration.getAllowedOrigins()).contains("*");
|
||||
@ -217,8 +217,7 @@ public class CloudFoundryActuatorAutoConfigurationTests {
|
||||
this.context.register(TestConfiguration.class);
|
||||
this.context.refresh();
|
||||
CloudFoundryWebEndpointServletHandlerMapping handlerMapping = getHandlerMapping();
|
||||
List<EndpointInfo<WebOperation>> endpoints = (List<EndpointInfo<WebOperation>>) handlerMapping
|
||||
.getEndpoints();
|
||||
Collection<ExposableWebEndpoint> endpoints = handlerMapping.getEndpoints();
|
||||
assertThat(endpoints.stream()
|
||||
.filter((candidate) -> "test".equals(candidate.getId())).findFirst())
|
||||
.isNotEmpty();
|
||||
@ -231,9 +230,8 @@ public class CloudFoundryActuatorAutoConfigurationTests {
|
||||
this.context.register(TestConfiguration.class);
|
||||
this.context.refresh();
|
||||
CloudFoundryWebEndpointServletHandlerMapping handlerMapping = getHandlerMapping();
|
||||
List<EndpointInfo<WebOperation>> endpoints = (List<EndpointInfo<WebOperation>>) handlerMapping
|
||||
.getEndpoints();
|
||||
EndpointInfo<WebOperation> endpoint = endpoints.stream()
|
||||
Collection<ExposableWebEndpoint> endpoints = handlerMapping.getEndpoints();
|
||||
ExposableWebEndpoint endpoint = endpoints.stream()
|
||||
.filter((candidate) -> "test".equals(candidate.getId())).findFirst()
|
||||
.get();
|
||||
Collection<WebOperation> operations = endpoint.getOperations();
|
||||
@ -249,14 +247,13 @@ public class CloudFoundryActuatorAutoConfigurationTests {
|
||||
"vcap.application.cf_api:http://my-cloud-controller.com")
|
||||
.applyTo(this.context);
|
||||
this.context.refresh();
|
||||
Collection<EndpointInfo<WebOperation>> endpoints = this.context
|
||||
Collection<ExposableWebEndpoint> endpoints = this.context
|
||||
.getBean("cloudFoundryWebEndpointServletHandlerMapping",
|
||||
CloudFoundryWebEndpointServletHandlerMapping.class)
|
||||
.getEndpoints();
|
||||
EndpointInfo<WebOperation> endpointInfo = endpoints.iterator().next();
|
||||
WebOperation webOperation = endpointInfo.getOperations().iterator().next();
|
||||
ReflectiveOperationInvoker invoker = (ReflectiveOperationInvoker) webOperation
|
||||
.getInvoker();
|
||||
ExposableWebEndpoint endpoint = endpoints.iterator().next();
|
||||
WebOperation webOperation = endpoint.getOperations().iterator().next();
|
||||
Object invoker = ReflectionTestUtils.getField(webOperation, "invoker");
|
||||
assertThat(ReflectionTestUtils.getField(invoker, "target"))
|
||||
.isInstanceOf(CloudFoundryHealthEndpointWebExtension.class);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -27,17 +27,15 @@ import org.junit.Test;
|
||||
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel;
|
||||
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException;
|
||||
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException.Reason;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
|
||||
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
|
||||
@ -204,7 +202,7 @@ public class CloudFoundryMvcWebEndpointIntegrationTests {
|
||||
|
||||
@Bean
|
||||
public CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandlerMapping(
|
||||
EndpointDiscoverer<WebOperation> webEndpointDiscoverer,
|
||||
WebEndpointDiscoverer webEndpointDiscoverer,
|
||||
EndpointMediaTypes endpointMediaTypes,
|
||||
CloudFoundrySecurityInterceptor interceptor) {
|
||||
CorsConfiguration corsConfiguration = new CorsConfiguration();
|
||||
@ -212,19 +210,19 @@ public class CloudFoundryMvcWebEndpointIntegrationTests {
|
||||
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST"));
|
||||
return new CloudFoundryWebEndpointServletHandlerMapping(
|
||||
new EndpointMapping("/cfApplication"),
|
||||
webEndpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
|
||||
webEndpointDiscoverer.getEndpoints(), endpointMediaTypes,
|
||||
corsConfiguration, interceptor);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebAnnotationEndpointDiscoverer webEndpointDiscoverer(
|
||||
public WebEndpointDiscoverer webEndpointDiscoverer(
|
||||
ApplicationContext applicationContext,
|
||||
EndpointMediaTypes endpointMediaTypes) {
|
||||
ParameterMapper parameterMapper = new ConversionServiceParameterMapper(
|
||||
ParameterValueMapper parameterMapper = new ConversionServiceParameterValueMapper(
|
||||
DefaultConversionService.getSharedInstance());
|
||||
return new WebAnnotationEndpointDiscoverer(applicationContext,
|
||||
parameterMapper, endpointMediaTypes,
|
||||
EndpointPathResolver.useEndpointId(), null, null);
|
||||
return new WebEndpointDiscoverer(applicationContext, parameterMapper,
|
||||
endpointMediaTypes, EndpointPathResolver.useEndpointId(),
|
||||
Collections.emptyList(), Collections.emptyList());
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
@ -16,22 +16,20 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.endpoint;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointFilter;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.Operation;
|
||||
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link ExposeExcludePropertyEndpointFilter}.
|
||||
@ -43,12 +41,7 @@ public class ExposeExcludePropertyEndpointFilterTests {
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private MockEnvironment environment = new MockEnvironment();
|
||||
|
||||
private EndpointFilter<Operation> filter;
|
||||
|
||||
@Mock
|
||||
private TestEndpointDiscoverer discoverer;
|
||||
private ExposeExcludePropertyEndpointFilter<?> filter;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
@ -56,34 +49,33 @@ public class ExposeExcludePropertyEndpointFilterTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createWhenDiscovererTypeIsNullShouldThrowException() {
|
||||
public void createWhenEndpointTypeIsNullShouldThrowException() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Discoverer Type must not be null");
|
||||
new ExposeExcludePropertyEndpointFilter<>(null, this.environment, "foo");
|
||||
this.thrown.expectMessage("EndpointType must not be null");
|
||||
new ExposeExcludePropertyEndpointFilter<>(null, new MockEnvironment(), "foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createWhenEnvironmentIsNullShouldThrowException() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Environment must not be null");
|
||||
new ExposeExcludePropertyEndpointFilter<>(TestEndpointDiscoverer.class, null,
|
||||
"foo");
|
||||
new ExposeExcludePropertyEndpointFilter<>(ExposableEndpoint.class, null, "foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createWhenPrefixIsNullShouldThrowException() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Prefix must not be empty");
|
||||
new ExposeExcludePropertyEndpointFilter<>(TestEndpointDiscoverer.class,
|
||||
this.environment, null);
|
||||
new ExposeExcludePropertyEndpointFilter<>(ExposableEndpoint.class,
|
||||
new MockEnvironment(), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createWhenPrefixIsEmptyShouldThrowException() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Prefix must not be empty");
|
||||
new ExposeExcludePropertyEndpointFilter<>(TestEndpointDiscoverer.class,
|
||||
this.environment, "");
|
||||
new ExposeExcludePropertyEndpointFilter<>(ExposableEndpoint.class,
|
||||
new MockEnvironment(), "");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -130,10 +122,11 @@ public class ExposeExcludePropertyEndpointFilterTests {
|
||||
|
||||
@Test
|
||||
public void matchWhenDiscovererDoesNotMatchShouldMatch() {
|
||||
this.environment.setProperty("foo.expose", "bar");
|
||||
this.environment.setProperty("foo.exclude", "");
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
environment.setProperty("foo.expose", "bar");
|
||||
environment.setProperty("foo.exclude", "");
|
||||
this.filter = new ExposeExcludePropertyEndpointFilter<>(
|
||||
DifferentTestEndpointDiscoverer.class, this.environment, "foo");
|
||||
DifferentTestExposableWebEndpoint.class, environment, "foo");
|
||||
assertThat(match("baz")).isTrue();
|
||||
}
|
||||
|
||||
@ -154,25 +147,27 @@ public class ExposeExcludePropertyEndpointFilterTests {
|
||||
}
|
||||
|
||||
private void setupFilter(String expose, String exclude) {
|
||||
this.environment.setProperty("foo.expose", expose);
|
||||
this.environment.setProperty("foo.exclude", exclude);
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
environment.setProperty("foo.expose", expose);
|
||||
environment.setProperty("foo.exclude", exclude);
|
||||
this.filter = new ExposeExcludePropertyEndpointFilter<>(
|
||||
TestEndpointDiscoverer.class, this.environment, "foo", "def");
|
||||
TestExposableWebEndpoint.class, environment, "foo", "def");
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private boolean match(String id) {
|
||||
EndpointInfo<Operation> info = new EndpointInfo<>(id, true,
|
||||
Collections.emptyList());
|
||||
return this.filter.match(info, this.discoverer);
|
||||
ExposableEndpoint<?> endpoint = mock(TestExposableWebEndpoint.class);
|
||||
given(endpoint.getId()).willReturn(id);
|
||||
return ((EndpointFilter) this.filter).match(endpoint);
|
||||
}
|
||||
|
||||
private abstract static class TestEndpointDiscoverer
|
||||
implements EndpointDiscoverer<Operation> {
|
||||
private abstract static class TestExposableWebEndpoint
|
||||
implements ExposableWebEndpoint {
|
||||
|
||||
}
|
||||
|
||||
private abstract static class DifferentTestEndpointDiscoverer
|
||||
implements EndpointDiscoverer<Operation> {
|
||||
private abstract static class DifferentTestExposableWebEndpoint
|
||||
implements ExposableWebEndpoint {
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -18,10 +18,8 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.condition;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointFilter;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.Operation;
|
||||
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
@ -124,11 +122,10 @@ public class ConditionalOnEnabledEndpointTests {
|
||||
|
||||
}
|
||||
|
||||
static class TestFilter implements EndpointFilter<Operation> {
|
||||
static class TestFilter implements EndpointFilter<ExposableEndpoint<?>> {
|
||||
|
||||
@Override
|
||||
public boolean match(EndpointInfo<Operation> info,
|
||||
EndpointDiscoverer<Operation> discoverer) {
|
||||
public boolean match(ExposableEndpoint<?> endpoint) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -24,7 +24,7 @@ import javax.management.ObjectName;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.jmx.EndpointMBean;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.ExposableJmxEndpoint;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
@ -73,7 +73,7 @@ public class DefaultEndpointObjectNameFactoryTests {
|
||||
@Test
|
||||
public void generateObjectNameWithUniqueNames() {
|
||||
this.properties.setUniqueNames(true);
|
||||
EndpointMBean endpoint = endpoint("test");
|
||||
ExposableJmxEndpoint endpoint = endpoint("test");
|
||||
String id = ObjectUtils.getIdentityHexString(endpoint);
|
||||
ObjectName objectName = generateObjectName(endpoint);
|
||||
assertThat(objectName.toString()).isEqualTo(
|
||||
@ -105,20 +105,20 @@ public class DefaultEndpointObjectNameFactoryTests {
|
||||
|
||||
}
|
||||
|
||||
private ObjectName generateObjectName(EndpointMBean endpointMBean) {
|
||||
private ObjectName generateObjectName(ExposableJmxEndpoint endpoint) {
|
||||
try {
|
||||
return new DefaultEndpointObjectNameFactory(this.properties, this.mBeanServer,
|
||||
this.contextId).generate(endpointMBean);
|
||||
this.contextId).getObjectName(endpoint);
|
||||
}
|
||||
catch (MalformedObjectNameException ex) {
|
||||
throw new AssertionError("Invalid object name", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private EndpointMBean endpoint(String id) {
|
||||
EndpointMBean endpointMBean = mock(EndpointMBean.class);
|
||||
given(endpointMBean.getEndpointId()).willReturn(id);
|
||||
return endpointMBean;
|
||||
private ExposableJmxEndpoint endpoint(String id) {
|
||||
ExposableJmxEndpoint endpoint = mock(ExposableJmxEndpoint.class);
|
||||
given(endpoint.getId()).willReturn(id);
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -18,19 +18,16 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultEndpointPathProvider}.
|
||||
@ -39,9 +36,6 @@ import static org.mockito.BDDMockito.given;
|
||||
*/
|
||||
public class DefaultEndpointPathProviderTests {
|
||||
|
||||
@Mock
|
||||
private EndpointDiscoverer<WebOperation> discoverer;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
@ -78,13 +72,18 @@ public class DefaultEndpointPathProviderTests {
|
||||
}
|
||||
|
||||
private DefaultEndpointPathProvider createProvider(String contextPath) {
|
||||
Collection<EndpointInfo<WebOperation>> endpoints = new ArrayList<>();
|
||||
endpoints.add(new EndpointInfo<>("foo", true, Collections.emptyList()));
|
||||
endpoints.add(new EndpointInfo<>("bar", true, Collections.emptyList()));
|
||||
given(this.discoverer.discoverEndpoints()).willReturn(endpoints);
|
||||
WebEndpointProperties webEndpointProperties = new WebEndpointProperties();
|
||||
webEndpointProperties.setBasePath(contextPath);
|
||||
return new DefaultEndpointPathProvider(this.discoverer, webEndpointProperties);
|
||||
Collection<ExposableWebEndpoint> endpoints = new ArrayList<>();
|
||||
endpoints.add(mockEndpoint("foo"));
|
||||
endpoints.add(mockEndpoint("bar"));
|
||||
WebEndpointProperties properties = new WebEndpointProperties();
|
||||
properties.setBasePath(contextPath);
|
||||
return new DefaultEndpointPathProvider(properties, endpoints);
|
||||
}
|
||||
|
||||
private ExposableWebEndpoint mockEndpoint(String id) {
|
||||
ExposableWebEndpoint endpoint = mock(ExposableWebEndpoint.class);
|
||||
given(endpoint.getId()).willReturn(id);
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link ExposableEndpoint} implementations.
|
||||
*
|
||||
* @param <O> The operation type.
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public abstract class AbstractExposableEndpoint<O extends Operation>
|
||||
implements ExposableEndpoint<O> {
|
||||
|
||||
private final String id;
|
||||
|
||||
private boolean enabledByDefault;
|
||||
|
||||
private List<O> operations;
|
||||
|
||||
/**
|
||||
* Create a new {@link AbstractExposableEndpoint} instance.
|
||||
* @param id the endpoint id
|
||||
* @param enabledByDefault if the endpoint is enabled by default
|
||||
* @param operations the endpoint operations
|
||||
*/
|
||||
public AbstractExposableEndpoint(String id, boolean enabledByDefault,
|
||||
Collection<? extends O> operations) {
|
||||
Assert.notNull(id, "ID must not be null");
|
||||
Assert.notNull(operations, "Operations must not be null");
|
||||
this.id = id;
|
||||
this.enabledByDefault = enabledByDefault;
|
||||
this.operations = Collections.unmodifiableList(new ArrayList<>(operations));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnableByDefault() {
|
||||
return this.enabledByDefault;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<O> getOperations() {
|
||||
return this.operations;
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -17,21 +17,20 @@
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
|
||||
/**
|
||||
* Strategy class that can be used to filter discovered endpoints.
|
||||
* Strategy class that can be used to filter {@link ExposableEndpoint endpoints}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @param <T> the type of the endpoint's operations
|
||||
* @param <E> the endpoint type
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface EndpointFilter<T extends Operation> {
|
||||
public interface EndpointFilter<E extends ExposableEndpoint<?>> {
|
||||
|
||||
/**
|
||||
* Return {@code true} if the filter matches.
|
||||
* @param info the endpoint info
|
||||
* @param discoverer the endpoint discoverer
|
||||
* @param endpoint the endpoint to check
|
||||
* @return {@code true} if the filter matches
|
||||
*/
|
||||
boolean match(EndpointInfo<T> info, EndpointDiscoverer<T> discoverer);
|
||||
boolean match(E endpoint);
|
||||
|
||||
}
|
||||
|
@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Information describing an endpoint.
|
||||
*
|
||||
* @param <T> the type of the endpoint's operations
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class EndpointInfo<T extends Operation> {
|
||||
|
||||
private final String id;
|
||||
|
||||
private final boolean enableByDefault;
|
||||
|
||||
private final Collection<T> operations;
|
||||
|
||||
/**
|
||||
* Creates a new {@code EndpointInfo} describing an endpoint with the given {@code id}
|
||||
* and {@code operations}.
|
||||
* @param id the id of the endpoint
|
||||
* @param enableByDefault if the endpoint is enabled by default
|
||||
* @param operations the operations of the endpoint
|
||||
*/
|
||||
public EndpointInfo(String id, boolean enableByDefault, Collection<T> operations) {
|
||||
Assert.hasText(id, "ID must not be empty");
|
||||
Assert.notNull(operations, "Operations must not be null");
|
||||
this.id = id;
|
||||
this.enableByDefault = enableByDefault;
|
||||
this.operations = Collections.unmodifiableCollection(operations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id of the endpoint.
|
||||
* @return the id
|
||||
*/
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the endpoint is enabled by default.
|
||||
* @return if the endpoint is enabled by default
|
||||
*/
|
||||
public boolean isEnableByDefault() {
|
||||
return this.enableByDefault;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the operations of the endpoint.
|
||||
* @return the operations
|
||||
*/
|
||||
public Collection<T> getOperations() {
|
||||
return this.operations;
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -19,20 +19,20 @@ package org.springframework.boot.actuate.endpoint;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Discovers endpoints and provides an {@link EndpointInfo} for each of them.
|
||||
* Provides access to a collection of {@link ExposableEndpoint endpoints}.
|
||||
*
|
||||
* @param <T> the type of the operation
|
||||
* @param <E> the endpoint type
|
||||
* @author Andy Wilkinson
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface EndpointDiscoverer<T extends Operation> {
|
||||
public interface EndpointsSupplier<E extends ExposableEndpoint<?>> {
|
||||
|
||||
/**
|
||||
* Perform endpoint discovery.
|
||||
* @return the discovered endpoints
|
||||
* Return the provided endpoints.
|
||||
* @return the endpoints
|
||||
*/
|
||||
Collection<EndpointInfo<T>> discoverEndpoints();
|
||||
Collection<E> getEndpoints();
|
||||
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Information describing an endpoint that can be exposed in some technology specific way.
|
||||
*
|
||||
* @param <O> the type of the endpoint's operations
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public interface ExposableEndpoint<O extends Operation> {
|
||||
|
||||
/**
|
||||
* Returns the id of the endpoint.
|
||||
* @return the id
|
||||
*/
|
||||
String getId();
|
||||
|
||||
/**
|
||||
* Returns if the endpoint is enabled by default.
|
||||
* @return if the endpoint is enabled by default
|
||||
*/
|
||||
boolean isEnableByDefault();
|
||||
|
||||
/**
|
||||
* Returns the operations of the endpoint.
|
||||
* @return the operations
|
||||
*/
|
||||
Collection<O> getOperations();
|
||||
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
/**
|
||||
* Utility class that can be used to filter source data using a name regular expression.
|
||||
* Detects if the name is classic "single value" key or a regular expression. Subclasses
|
||||
* must provide implementations of {@link #getValue(Object, String)} and
|
||||
* {@link #getNames(Object, NameCallback)}.
|
||||
*
|
||||
* @param <T> the source data type
|
||||
* @author Phillip Webb
|
||||
* @author Sergei Egorov
|
||||
* @author Andy Wilkinson
|
||||
* @author Dylian Bego
|
||||
*/
|
||||
abstract class NamePatternFilter<T> {
|
||||
|
||||
private static final String[] REGEX_PARTS = { "*", "$", "^", "+", "[" };
|
||||
|
||||
private final T source;
|
||||
|
||||
NamePatternFilter(T source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public Map<String, Object> getResults(String name) {
|
||||
Pattern pattern = compilePatternIfNecessary(name);
|
||||
if (pattern == null) {
|
||||
Object value = getValue(this.source, name);
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put(name, value);
|
||||
return result;
|
||||
}
|
||||
ResultCollectingNameCallback resultCollector = new ResultCollectingNameCallback(
|
||||
pattern);
|
||||
getNames(this.source, resultCollector);
|
||||
return resultCollector.getResults();
|
||||
|
||||
}
|
||||
|
||||
private Pattern compilePatternIfNecessary(String name) {
|
||||
for (String part : REGEX_PARTS) {
|
||||
if (name.contains(part)) {
|
||||
try {
|
||||
return Pattern.compile(name);
|
||||
}
|
||||
catch (PatternSyntaxException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected abstract void getNames(T source, NameCallback callback);
|
||||
|
||||
protected abstract Object getValue(T source, String name);
|
||||
|
||||
protected abstract Object getOptionalValue(T source, String name);
|
||||
|
||||
/**
|
||||
* Callback used to add a name.
|
||||
*/
|
||||
interface NameCallback {
|
||||
|
||||
void addName(String name);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link NameCallback} implementation to collect results.
|
||||
*/
|
||||
private class ResultCollectingNameCallback implements NameCallback {
|
||||
|
||||
private final Pattern pattern;
|
||||
|
||||
private final Map<String, Object> results = new LinkedHashMap<>();
|
||||
|
||||
ResultCollectingNameCallback(Pattern pattern) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addName(String name) {
|
||||
if (this.pattern.matcher(name).matches()) {
|
||||
Object value = getOptionalValue(NamePatternFilter.this.source, name);
|
||||
if (value != null) {
|
||||
this.results.put(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> getResults() {
|
||||
return this.results;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -16,58 +16,28 @@
|
||||
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An operation on an endpoint.
|
||||
* An operation on an {@link ExposableEndpoint endpoint}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class Operation {
|
||||
|
||||
private final OperationType type;
|
||||
|
||||
private final OperationInvoker invoker;
|
||||
|
||||
private final boolean blocking;
|
||||
|
||||
/**
|
||||
* Creates a new {@code EndpointOperation} for an operation of the given {@code type}.
|
||||
* The operation can be performed using the given {@code operationInvoker}.
|
||||
* @param operationType the type of the operation
|
||||
* @param invoker used to perform the operation
|
||||
* @param blocking whether or not this is a blocking operation
|
||||
*/
|
||||
public Operation(OperationType operationType, OperationInvoker invoker,
|
||||
boolean blocking) {
|
||||
this.type = operationType;
|
||||
this.invoker = invoker;
|
||||
this.blocking = blocking;
|
||||
}
|
||||
public interface Operation {
|
||||
|
||||
/**
|
||||
* Returns the {@link OperationType type} of the operation.
|
||||
* @return the type
|
||||
*/
|
||||
public OperationType getType() {
|
||||
return this.type;
|
||||
}
|
||||
OperationType getType();
|
||||
|
||||
/**
|
||||
* Returns the {@code OperationInvoker} that can be used to invoke this endpoint
|
||||
* operation.
|
||||
* @return the operation invoker
|
||||
* Invoke the underlying operation using the given {@code arguments}.
|
||||
* @param arguments the arguments to pass to the operation
|
||||
* @return the result of the operation, may be {@code null}
|
||||
*/
|
||||
public OperationInvoker getInvoker() {
|
||||
return this.invoker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not this is a blocking operation.
|
||||
*
|
||||
* @return {@code true} if it is a blocking operation, otherwise {@code false}.
|
||||
*/
|
||||
public boolean isBlocking() {
|
||||
return this.blocking;
|
||||
}
|
||||
Object invoke(Map<String, Object> arguments);
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -21,6 +21,7 @@ package org.springframework.boot.actuate.endpoint;
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
* @see Operation
|
||||
*/
|
||||
public enum OperationType {
|
||||
|
||||
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.annotation;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.AbstractExposableEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.Operation;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link ExposableEndpoint endpoints} discovered by a
|
||||
* {@link EndpointDiscoverer}.
|
||||
*
|
||||
* @param <O> The operation type
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public abstract class AbstractDiscoveredEndpoint<O extends Operation>
|
||||
extends AbstractExposableEndpoint<O> implements DiscoveredEndpoint<O> {
|
||||
|
||||
private final EndpointDiscoverer<?, ?> discoverer;
|
||||
|
||||
/**
|
||||
* Create a mew {@link AbstractDiscoveredEndpoint} instance.
|
||||
* @param discoverer the discoverer that discovered the endpoint
|
||||
* @param id the ID of the endpoint
|
||||
* @param enabledByDefault if the endpoint is enabled by default
|
||||
* @param operations the endpoint operations
|
||||
*/
|
||||
public AbstractDiscoveredEndpoint(EndpointDiscoverer<?, ?> discoverer, String id,
|
||||
boolean enabledByDefault, Collection<? extends O> operations) {
|
||||
super(id, enabledByDefault, operations);
|
||||
Assert.notNull(discoverer, "Discoverer must not be null");
|
||||
Assert.notNull(discoverer, "EndpointBean must not be null");
|
||||
this.discoverer = discoverer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wasDiscoveredBy(Class<? extends EndpointDiscoverer<?, ?>> discoverer) {
|
||||
return discoverer.isInstance(this.discoverer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ToStringCreator creator = new ToStringCreator(this).append("discoverer",
|
||||
this.discoverer.getClass().getName());
|
||||
appendFields(creator);
|
||||
return creator.toString();
|
||||
}
|
||||
|
||||
protected void appendFields(ToStringCreator creator) {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.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.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.reflect.OperationMethod;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link Operation endpoints operations} discovered by a
|
||||
* {@link EndpointDiscoverer}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public abstract class AbstractDiscoveredOperation implements Operation {
|
||||
|
||||
private final OperationMethod operationMethod;
|
||||
|
||||
private final OperationInvoker invoker;
|
||||
|
||||
/**
|
||||
* Create a new {@link AbstractDiscoveredOperation} instance.
|
||||
* @param operationMethod the method backing the operation
|
||||
* @param invoker the operation invoker to use
|
||||
*/
|
||||
public AbstractDiscoveredOperation(DiscoveredOperationMethod operationMethod,
|
||||
OperationInvoker invoker) {
|
||||
this.operationMethod = operationMethod;
|
||||
this.invoker = invoker;
|
||||
}
|
||||
|
||||
public OperationMethod getOperationMethod() {
|
||||
return this.operationMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OperationType getType() {
|
||||
return this.operationMethod.getOperationType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Map<String, Object> arguments) {
|
||||
return this.invoker.invoke(arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ToStringCreator creator = new ToStringCreator(this)
|
||||
.append("operationMethod", this.operationMethod)
|
||||
.append("invoker", this.invoker);
|
||||
appendFields(creator);
|
||||
return creator.toString();
|
||||
}
|
||||
|
||||
protected void appendFields(ToStringCreator creator) {
|
||||
}
|
||||
|
||||
}
|
@ -1,517 +0,0 @@
|
||||
/*
|
||||
* 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.annotation;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointFilter;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.Operation;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInvokerAdvisor;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A base {@link EndpointDiscoverer} implementation that discovers
|
||||
* {@link Endpoint @Endpoint} beans and {@link EndpointExtension @EndpointExtension} beans
|
||||
* in an application context.
|
||||
*
|
||||
* @param <K> the type of the operation key
|
||||
* @param <T> the type of the operation
|
||||
* @author Andy Wilkinson
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public abstract class AnnotationEndpointDiscoverer<K, T extends Operation>
|
||||
implements EndpointDiscoverer<T> {
|
||||
|
||||
private static final Log logger = LogFactory
|
||||
.getLog(AnnotationEndpointDiscoverer.class);
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
private final Function<T, K> operationKeyFactory;
|
||||
|
||||
private final OperationsFactory<T> operationsFactory;
|
||||
|
||||
private final List<EndpointFilter<T>> filters;
|
||||
|
||||
/**
|
||||
* Create a new {@link AnnotationEndpointDiscoverer} instance.
|
||||
* @param applicationContext the application context
|
||||
* @param operationFactory a factory used to create operations
|
||||
* @param operationKeyFactory a factory used to create a key for an operation
|
||||
* @param parameterMapper the {@link ParameterMapper} used to convert arguments when
|
||||
* an operation is invoked
|
||||
* @param invokerAdvisors advisors used to add additional invoker advise
|
||||
* @param filters filters that must match for an endpoint to be exposed
|
||||
*/
|
||||
protected AnnotationEndpointDiscoverer(ApplicationContext applicationContext,
|
||||
OperationFactory<T> operationFactory, Function<T, K> operationKeyFactory,
|
||||
ParameterMapper parameterMapper,
|
||||
Collection<? extends OperationMethodInvokerAdvisor> invokerAdvisors,
|
||||
Collection<? extends EndpointFilter<T>> filters) {
|
||||
Assert.notNull(applicationContext, "Application Context must not be null");
|
||||
Assert.notNull(operationFactory, "Operation Factory must not be null");
|
||||
Assert.notNull(operationKeyFactory, "Operation Key Factory must not be null");
|
||||
Assert.notNull(parameterMapper, "Parameter Mapper must not be null");
|
||||
this.applicationContext = applicationContext;
|
||||
this.operationKeyFactory = operationKeyFactory;
|
||||
this.operationsFactory = new OperationsFactory<>(operationFactory,
|
||||
parameterMapper, invokerAdvisors);
|
||||
this.filters = (filters == null ? Collections.emptyList()
|
||||
: new ArrayList<>(filters));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Collection<EndpointInfo<T>> discoverEndpoints() {
|
||||
Class<T> operationType = getOperationType();
|
||||
Map<Class<?>, DiscoveredEndpoint> endpoints = getEndpoints(operationType);
|
||||
Map<Class<?>, DiscoveredExtension> extensions = getExtensions(operationType,
|
||||
endpoints);
|
||||
Collection<DiscoveredEndpoint> exposed = mergeExposed(endpoints, extensions);
|
||||
verify(exposed);
|
||||
return exposed.stream().map(DiscoveredEndpoint::getInfo)
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the operation type being discovered. By default this method will resolve the
|
||||
* class generic "{@code <T>}".
|
||||
* @return the operation type
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Class<T> getOperationType() {
|
||||
return (Class<T>) ResolvableType
|
||||
.forClass(AnnotationEndpointDiscoverer.class, getClass())
|
||||
.resolveGeneric(1);
|
||||
}
|
||||
|
||||
private Map<Class<?>, DiscoveredEndpoint> getEndpoints(Class<T> operationType) {
|
||||
Map<Class<?>, DiscoveredEndpoint> endpoints = new LinkedHashMap<>();
|
||||
Map<String, DiscoveredEndpoint> endpointsById = new LinkedHashMap<>();
|
||||
String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(
|
||||
this.applicationContext, Endpoint.class);
|
||||
for (String beanName : beanNames) {
|
||||
addEndpoint(endpoints, endpointsById, beanName);
|
||||
}
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
private void addEndpoint(Map<Class<?>, DiscoveredEndpoint> endpoints,
|
||||
Map<String, DiscoveredEndpoint> endpointsById, String beanName) {
|
||||
Class<?> endpointType = this.applicationContext.getType(beanName);
|
||||
Object target = this.applicationContext.getBean(beanName);
|
||||
DiscoveredEndpoint endpoint = createEndpoint(target, endpointType);
|
||||
String id = endpoint.getInfo().getId();
|
||||
DiscoveredEndpoint previous = endpointsById.putIfAbsent(id, endpoint);
|
||||
Assert.state(previous == null, () -> "Found two endpoints with the id '" + id
|
||||
+ "': " + endpoint + " and " + previous);
|
||||
endpoints.put(endpointType, endpoint);
|
||||
}
|
||||
|
||||
private DiscoveredEndpoint createEndpoint(Object target, Class<?> endpointType) {
|
||||
AnnotationAttributes annotationAttributes = AnnotatedElementUtils
|
||||
.findMergedAnnotationAttributes(endpointType, Endpoint.class, true, true);
|
||||
String id = annotationAttributes.getString("id");
|
||||
Assert.state(StringUtils.hasText(id),
|
||||
"No @Endpoint id attribute specified for " + endpointType.getName());
|
||||
boolean enabledByDefault = (Boolean) annotationAttributes.get("enableByDefault");
|
||||
Collection<T> operations = this.operationsFactory
|
||||
.createOperations(id, target, endpointType).values();
|
||||
EndpointInfo<T> endpointInfo = new EndpointInfo<>(id, enabledByDefault,
|
||||
operations);
|
||||
boolean exposed = isEndpointExposed(endpointType, endpointInfo);
|
||||
return new DiscoveredEndpoint(endpointType, endpointInfo, exposed);
|
||||
}
|
||||
|
||||
private Map<Class<?>, DiscoveredExtension> getExtensions(Class<T> operationType,
|
||||
Map<Class<?>, DiscoveredEndpoint> endpoints) {
|
||||
Map<Class<?>, DiscoveredExtension> extensions = new LinkedHashMap<>();
|
||||
String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(
|
||||
this.applicationContext, EndpointExtension.class);
|
||||
for (String beanName : beanNames) {
|
||||
addExtension(endpoints, extensions, beanName);
|
||||
}
|
||||
return extensions;
|
||||
}
|
||||
|
||||
private void addExtension(Map<Class<?>, DiscoveredEndpoint> endpoints,
|
||||
Map<Class<?>, DiscoveredExtension> extensions, String beanName) {
|
||||
Class<?> extensionType = this.applicationContext.getType(beanName);
|
||||
Class<?> endpointType = getEndpointType(extensionType);
|
||||
DiscoveredEndpoint endpoint = getExtendingEndpoint(endpoints, extensionType,
|
||||
endpointType);
|
||||
if (isExtensionExposed(endpointType, extensionType, endpoint.getInfo())) {
|
||||
Assert.state(endpoint.isExposed() || isEndpointFiltered(endpoint.getInfo()),
|
||||
() -> "Invalid extension " + extensionType.getName() + "': endpoint '"
|
||||
+ endpointType.getName()
|
||||
+ "' does not support such extension");
|
||||
Object target = this.applicationContext.getBean(beanName);
|
||||
Map<Method, T> operations = this.operationsFactory
|
||||
.createOperations(endpoint.getInfo().getId(), target, extensionType);
|
||||
DiscoveredExtension extension = new DiscoveredExtension(extensionType,
|
||||
operations.values());
|
||||
DiscoveredExtension previous = extensions.putIfAbsent(endpointType,
|
||||
extension);
|
||||
Assert.state(previous == null,
|
||||
() -> "Found two extensions for the same endpoint '"
|
||||
+ endpointType.getName() + "': "
|
||||
+ extension.getExtensionType().getName() + " and "
|
||||
+ previous.getExtensionType().getName());
|
||||
}
|
||||
}
|
||||
|
||||
private Class<?> getEndpointType(Class<?> extensionType) {
|
||||
AnnotationAttributes attributes = AnnotatedElementUtils
|
||||
.getMergedAnnotationAttributes(extensionType, EndpointExtension.class);
|
||||
Class<?> endpointType = attributes.getClass("endpoint");
|
||||
Assert.state(!endpointType.equals(Void.class), () -> "Extension "
|
||||
+ endpointType.getName() + " does not specify an endpoint");
|
||||
return endpointType;
|
||||
}
|
||||
|
||||
private DiscoveredEndpoint getExtendingEndpoint(
|
||||
Map<Class<?>, DiscoveredEndpoint> endpoints, Class<?> extensionType,
|
||||
Class<?> endpointType) {
|
||||
DiscoveredEndpoint endpoint = endpoints.get(endpointType);
|
||||
Assert.state(endpoint != null,
|
||||
() -> "Invalid extension '" + extensionType.getName()
|
||||
+ "': no endpoint found with type '" + endpointType.getName()
|
||||
+ "'");
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
private boolean isEndpointExposed(Class<?> endpointType,
|
||||
EndpointInfo<T> endpointInfo) {
|
||||
if (isEndpointFiltered(endpointInfo)) {
|
||||
return false;
|
||||
}
|
||||
AnnotationAttributes annotationAttributes = AnnotatedElementUtils
|
||||
.getMergedAnnotationAttributes(endpointType, FilteredEndpoint.class);
|
||||
if (annotationAttributes == null) {
|
||||
return true;
|
||||
}
|
||||
Class<?> filterClass = annotationAttributes.getClass("value");
|
||||
return isFilterMatch(filterClass, endpointInfo);
|
||||
}
|
||||
|
||||
private boolean isEndpointFiltered(EndpointInfo<T> endpointInfo) {
|
||||
for (EndpointFilter<T> filter : this.filters) {
|
||||
if (!isFilterMatch(filter, endpointInfo)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if an extension is exposed.
|
||||
* @param endpointType the endpoint type
|
||||
* @param extensionType the extension type
|
||||
* @param endpointInfo the endpoint info
|
||||
* @return if the extension is exposed
|
||||
*/
|
||||
protected boolean isExtensionExposed(Class<?> endpointType, Class<?> extensionType,
|
||||
EndpointInfo<T> endpointInfo) {
|
||||
AnnotationAttributes annotationAttributes = AnnotatedElementUtils
|
||||
.getMergedAnnotationAttributes(extensionType, EndpointExtension.class);
|
||||
Class<?> filterClass = annotationAttributes.getClass("filter");
|
||||
return isFilterMatch(filterClass, endpointInfo);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private boolean isFilterMatch(Class<?> filterClass, EndpointInfo<T> endpointInfo) {
|
||||
Class<?> generic = ResolvableType.forClass(EndpointFilter.class, filterClass)
|
||||
.resolveGeneric(0);
|
||||
if (generic == null || generic.isAssignableFrom(getOperationType())) {
|
||||
EndpointFilter<T> filter = (EndpointFilter<T>) BeanUtils
|
||||
.instantiateClass(filterClass);
|
||||
return isFilterMatch(filter, endpointInfo);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isFilterMatch(EndpointFilter<T> filter,
|
||||
EndpointInfo<T> endpointInfo) {
|
||||
try {
|
||||
return filter.match(endpointInfo, this);
|
||||
}
|
||||
catch (ClassCastException ex) {
|
||||
String msg = ex.getMessage();
|
||||
if (msg == null || msg.startsWith(endpointInfo.getClass().getName())) {
|
||||
// Possibly a lambda-defined EndpointFilter which we could not resolve the
|
||||
// generic EndpointInfo type for
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(
|
||||
"Non-matching EndpointInfo for EndpointFilter: " + filter,
|
||||
ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Collection<DiscoveredEndpoint> mergeExposed(
|
||||
Map<Class<?>, DiscoveredEndpoint> endpoints,
|
||||
Map<Class<?>, DiscoveredExtension> extensions) {
|
||||
List<DiscoveredEndpoint> result = new ArrayList<>();
|
||||
endpoints.forEach((endpointClass, endpoint) -> {
|
||||
if (endpoint.isExposed()) {
|
||||
DiscoveredExtension extension = extensions.remove(endpointClass);
|
||||
result.add(endpoint.merge(extension));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows subclasses to verify that the descriptors are correctly configured.
|
||||
* @param exposedEndpoints the discovered endpoints to verify before exposing
|
||||
*/
|
||||
protected void verify(Collection<DiscoveredEndpoint> exposedEndpoints) {
|
||||
}
|
||||
|
||||
/**
|
||||
* A discovered endpoint (which may not be valid and might not ultimately be exposed).
|
||||
*/
|
||||
protected final class DiscoveredEndpoint {
|
||||
|
||||
private final EndpointInfo<T> info;
|
||||
|
||||
private final boolean exposed;
|
||||
|
||||
private final Map<OperationKey, List<T>> operations;
|
||||
|
||||
private DiscoveredEndpoint(Class<?> type, EndpointInfo<T> info, boolean exposed) {
|
||||
Assert.notNull(info, "Info must not be null");
|
||||
this.info = info;
|
||||
this.exposed = exposed;
|
||||
this.operations = indexEndpointOperations(type, info);
|
||||
}
|
||||
|
||||
private Map<OperationKey, List<T>> indexEndpointOperations(Class<?> endpointType,
|
||||
EndpointInfo<T> info) {
|
||||
return Collections.unmodifiableMap(
|
||||
indexOperations(info.getId(), endpointType, info.getOperations()));
|
||||
}
|
||||
|
||||
private DiscoveredEndpoint(EndpointInfo<T> info, boolean exposed,
|
||||
Map<OperationKey, List<T>> operations) {
|
||||
Assert.notNull(info, "Info must not be null");
|
||||
this.info = info;
|
||||
this.exposed = exposed;
|
||||
this.operations = operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link EndpointInfo} for the discovered endpoint.
|
||||
* @return the endpoint info
|
||||
*/
|
||||
public EndpointInfo<T> getInfo() {
|
||||
return this.info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if the endpoint is exposed.
|
||||
* @return if the is exposed
|
||||
*/
|
||||
private boolean isExposed() {
|
||||
return this.exposed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all operation that were discovered. These might be different to the ones
|
||||
* that are in {@link #getInfo()}.
|
||||
* @return the endpoint operations
|
||||
*/
|
||||
public Map<OperationKey, List<T>> getOperations() {
|
||||
return this.operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find any duplicate operations.
|
||||
* @return any duplicate operations
|
||||
*/
|
||||
public Map<OperationKey, List<T>> findDuplicateOperations() {
|
||||
return this.operations.entrySet().stream()
|
||||
.filter((entry) -> entry.getValue().size() > 1)
|
||||
.collect(Collectors.toMap(Entry::getKey, Entry::getValue, (u, v) -> v,
|
||||
LinkedHashMap::new));
|
||||
}
|
||||
|
||||
private DiscoveredEndpoint merge(DiscoveredExtension extension) {
|
||||
if (extension == null) {
|
||||
return this;
|
||||
}
|
||||
Map<OperationKey, List<T>> operations = mergeOperations(extension);
|
||||
EndpointInfo<T> info = new EndpointInfo<>(this.info.getId(),
|
||||
this.info.isEnableByDefault(), flatten(operations).values());
|
||||
return new DiscoveredEndpoint(info, this.exposed, operations);
|
||||
}
|
||||
|
||||
private Map<OperationKey, List<T>> mergeOperations(
|
||||
DiscoveredExtension extension) {
|
||||
MultiValueMap<OperationKey, T> operations = new LinkedMultiValueMap<>(
|
||||
this.operations);
|
||||
operations.addAll(indexOperations(getInfo().getId(),
|
||||
extension.getExtensionType(), extension.getOperations()));
|
||||
return Collections.unmodifiableMap(operations);
|
||||
}
|
||||
|
||||
private Map<K, T> flatten(Map<OperationKey, List<T>> operations) {
|
||||
Map<K, T> flattened = new LinkedHashMap<>();
|
||||
operations.forEach((operationKey, value) -> flattened
|
||||
.put(operationKey.getKey(), getLastValue(value)));
|
||||
return Collections.unmodifiableMap(flattened);
|
||||
}
|
||||
|
||||
private T getLastValue(List<T> value) {
|
||||
return value.get(value.size() - 1);
|
||||
}
|
||||
|
||||
private MultiValueMap<OperationKey, T> indexOperations(String endpointId,
|
||||
Class<?> target, Collection<T> operations) {
|
||||
LinkedMultiValueMap<OperationKey, T> result = new LinkedMultiValueMap<>();
|
||||
operations.forEach((operation) -> {
|
||||
K key = getOperationKey(operation);
|
||||
result.add(new OperationKey(endpointId, target, key), operation);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private K getOperationKey(T operation) {
|
||||
return AnnotationEndpointDiscoverer.this.operationKeyFactory.apply(operation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getInfo().toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A discovered extension.
|
||||
*/
|
||||
protected final class DiscoveredExtension {
|
||||
|
||||
private final Class<?> extensionType;
|
||||
|
||||
private final Collection<T> operations;
|
||||
|
||||
private DiscoveredExtension(Class<?> extensionType, Collection<T> operations) {
|
||||
this.extensionType = extensionType;
|
||||
this.operations = operations;
|
||||
}
|
||||
|
||||
public Class<?> getExtensionType() {
|
||||
return this.extensionType;
|
||||
}
|
||||
|
||||
public Collection<T> getOperations() {
|
||||
return this.operations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.extensionType.getName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the key of an operation in the context of an operation's implementation.
|
||||
*/
|
||||
protected final class OperationKey {
|
||||
|
||||
private final String endpointId;
|
||||
|
||||
private final Class<?> target;
|
||||
|
||||
private final K key;
|
||||
|
||||
public OperationKey(String endpointId, Class<?> target, K key) {
|
||||
this.endpointId = endpointId;
|
||||
this.target = target;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public K getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
OperationKey other = (OperationKey) o;
|
||||
Boolean result = true;
|
||||
result = result && this.endpointId.equals(other.endpointId);
|
||||
result = result && this.target.equals(other.target);
|
||||
result = result && this.key.equals(other.key);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = this.endpointId.hashCode();
|
||||
result = 31 * result + this.target.hashCode();
|
||||
result = 31 * result + this.key.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("endpointId", this.endpointId)
|
||||
.append("target", this.target).append("key", this.key).toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.annotation;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.Operation;
|
||||
|
||||
/**
|
||||
* An {@link ExposableEndpoint endpoint} discovered by a {@link EndpointDiscoverer}.
|
||||
*
|
||||
* @param <O> The operation type
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public interface DiscoveredEndpoint<O extends Operation> extends ExposableEndpoint<O> {
|
||||
|
||||
/**
|
||||
* Return {@code true} if the endpoint was discovered by the specified discoverer.
|
||||
* @param discoverer the discoverer type
|
||||
* @return {@code true} if discovered using the specified discoverer
|
||||
*/
|
||||
boolean wasDiscoveredBy(Class<? extends EndpointDiscoverer<?, ?>> discoverer);
|
||||
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.annotation;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.reflect.OperationMethod;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link OperationMethod} discovered by a {@link EndpointDiscoverer}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class DiscoveredOperationMethod extends OperationMethod {
|
||||
|
||||
private final List<String> producesMediaTypes;
|
||||
|
||||
public DiscoveredOperationMethod(Method method, OperationType operationType,
|
||||
AnnotationAttributes annotationAttributes) {
|
||||
super(method, operationType);
|
||||
Assert.notNull(annotationAttributes, "AnnotationAttributes must not be null");
|
||||
String[] produces = annotationAttributes.getStringArray("produces");
|
||||
this.producesMediaTypes = Collections.unmodifiableList(Arrays.asList(produces));
|
||||
}
|
||||
|
||||
public List<String> getProducesMediaTypes() {
|
||||
return this.producesMediaTypes;
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -18,7 +18,6 @@ package org.springframework.boot.actuate.endpoint.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
@ -26,12 +25,12 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.Operation;
|
||||
import org.springframework.boot.actuate.endpoint.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInfo;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInvokerAdvisor;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.ReflectiveOperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.reflect.OperationMethod;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.reflect.ReflectiveOperationInvoker;
|
||||
import org.springframework.core.MethodIntrospector;
|
||||
import org.springframework.core.MethodIntrospector.MetadataLookup;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
@ -39,14 +38,14 @@ import org.springframework.core.annotation.AnnotationAttributes;
|
||||
|
||||
/**
|
||||
* Factory to creates an {@link Operation} for a annotated methods on an
|
||||
* {@link Endpoint @Endpoint}.
|
||||
* {@link Endpoint @Endpoint} or {@link EndpointExtension @EndpointExtension}.
|
||||
*
|
||||
* @param <T> The operation type
|
||||
* @param <O> The operation type
|
||||
* @author Andy Wilkinson
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class OperationsFactory<T extends Operation> {
|
||||
abstract class DiscoveredOperationsFactory<O extends Operation> {
|
||||
|
||||
private static final Map<OperationType, Class<? extends Annotation>> OPERATION_TYPES;
|
||||
|
||||
@ -58,56 +57,56 @@ class OperationsFactory<T extends Operation> {
|
||||
OPERATION_TYPES = Collections.unmodifiableMap(operationTypes);
|
||||
}
|
||||
|
||||
private final OperationFactory<T> operationFactory;
|
||||
private final ParameterValueMapper parameterValueMapper;
|
||||
|
||||
private final ParameterMapper parameterMapper;
|
||||
private final Collection<OperationInvokerAdvisor> invokerAdvisors;
|
||||
|
||||
private final Collection<OperationMethodInvokerAdvisor> invokerAdvisors;
|
||||
|
||||
OperationsFactory(OperationFactory<T> operationFactory,
|
||||
ParameterMapper parameterMapper,
|
||||
Collection<? extends OperationMethodInvokerAdvisor> invokerAdvisors) {
|
||||
this.operationFactory = operationFactory;
|
||||
this.parameterMapper = parameterMapper;
|
||||
this.invokerAdvisors = (invokerAdvisors == null ? Collections.emptyList()
|
||||
: new ArrayList<>(invokerAdvisors));
|
||||
DiscoveredOperationsFactory(ParameterValueMapper parameterValueMapper,
|
||||
Collection<OperationInvokerAdvisor> invokerAdvisors) {
|
||||
this.parameterValueMapper = parameterValueMapper;
|
||||
this.invokerAdvisors = invokerAdvisors;
|
||||
}
|
||||
|
||||
public Map<Method, T> createOperations(String id, Object target, Class<?> type) {
|
||||
return MethodIntrospector.selectMethods(type,
|
||||
(MetadataLookup<T>) (method) -> createOperation(id, target, method));
|
||||
public Collection<O> createOperations(String id, Object target) {
|
||||
return MethodIntrospector.selectMethods(target.getClass(),
|
||||
(MetadataLookup<O>) (method) -> createOperation(id, target, method))
|
||||
.values();
|
||||
}
|
||||
|
||||
private T createOperation(String endpointId, Object target, Method method) {
|
||||
private O createOperation(String endpointId, Object target, Method method) {
|
||||
return OPERATION_TYPES.entrySet().stream()
|
||||
.map((entry) -> createOperation(endpointId, target, method,
|
||||
entry.getKey(), entry.getValue()))
|
||||
.filter(Objects::nonNull).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
private T createOperation(String endpointId, Object target, Method method,
|
||||
private O createOperation(String endpointId, Object target, Method method,
|
||||
OperationType operationType, Class<? extends Annotation> annotationType) {
|
||||
AnnotationAttributes annotationAttributes = AnnotatedElementUtils
|
||||
.getMergedAnnotationAttributes(method, annotationType);
|
||||
if (annotationAttributes == null) {
|
||||
return null;
|
||||
}
|
||||
OperationMethodInfo methodInfo = new OperationMethodInfo(method, operationType,
|
||||
annotationAttributes);
|
||||
OperationInvoker invoker = new ReflectiveOperationInvoker(target, methodInfo,
|
||||
this.parameterMapper);
|
||||
return this.operationFactory.createOperation(endpointId, methodInfo, target,
|
||||
applyAdvisors(endpointId, methodInfo, invoker));
|
||||
DiscoveredOperationMethod operationMethod = new DiscoveredOperationMethod(method,
|
||||
operationType, annotationAttributes);
|
||||
OperationInvoker invoker = new ReflectiveOperationInvoker(target, operationMethod,
|
||||
this.parameterValueMapper);
|
||||
invoker = applyAdvisors(endpointId, operationMethod, invoker);
|
||||
return createOperation(endpointId, operationMethod, invoker);
|
||||
}
|
||||
|
||||
private OperationInvoker applyAdvisors(String endpointId,
|
||||
OperationMethodInfo methodInfo, OperationInvoker invoker) {
|
||||
OperationMethod operationMethod, OperationInvoker invoker) {
|
||||
if (this.invokerAdvisors != null) {
|
||||
for (OperationMethodInvokerAdvisor advisor : this.invokerAdvisors) {
|
||||
invoker = advisor.apply(endpointId, methodInfo, invoker);
|
||||
for (OperationInvokerAdvisor advisor : this.invokerAdvisors) {
|
||||
invoker = advisor.apply(endpointId, operationMethod.getOperationType(),
|
||||
operationMethod.getParameters(), invoker);
|
||||
}
|
||||
}
|
||||
return invoker;
|
||||
}
|
||||
|
||||
protected abstract O createOperation(String endpointId,
|
||||
DiscoveredOperationMethod operationMethod, OperationInvoker invoker);
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.annotation;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointFilter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link EndpointFilter} the matches based on the {@link EndpointDiscoverer} the created
|
||||
* the endpoint.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public abstract class DiscovererEndpointFilter
|
||||
implements EndpointFilter<DiscoveredEndpoint<?>> {
|
||||
|
||||
private final Class<? extends EndpointDiscoverer<?, ?>> discoverer;
|
||||
|
||||
/**
|
||||
* Create a new {@link DiscovererEndpointFilter} instance.
|
||||
* @param discoverer the required discoverer
|
||||
*/
|
||||
protected DiscovererEndpointFilter(
|
||||
Class<? extends EndpointDiscoverer<?, ?>> discoverer) {
|
||||
Assert.notNull(discoverer, "Discoverer must not be null");
|
||||
this.discoverer = discoverer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(DiscoveredEndpoint<?> endpoint) {
|
||||
return endpoint.wasDiscoveredBy(this.discoverer);
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -44,7 +44,7 @@ import java.lang.annotation.Target;
|
||||
* @since 2.0.0
|
||||
* @see EndpointExtension
|
||||
* @see FilteredEndpoint
|
||||
* @see AnnotationEndpointDiscoverer
|
||||
* @see EndpointDiscoverer
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
|
@ -0,0 +1,525 @@
|
||||
/*
|
||||
* 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.annotation;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointFilter;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointsSupplier;
|
||||
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.Operation;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A Base for {@link EndpointsSupplier} implementations that discover
|
||||
* {@link Endpoint @Endpoint} beans and {@link EndpointExtension @EndpointExtension} beans
|
||||
* in an application context.
|
||||
*
|
||||
* @param <E> The endpoint type
|
||||
* @param <O> The operation type
|
||||
* @author Andy Wilkinson
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public abstract class EndpointDiscoverer<E extends ExposableEndpoint<O>, O extends Operation>
|
||||
implements EndpointsSupplier<E> {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(EndpointDiscoverer.class);
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
private final Collection<EndpointFilter<E>> filters;
|
||||
|
||||
private final DiscoveredOperationsFactory<O> operationsFactory;
|
||||
|
||||
private final Map<EndpointBean, E> filterEndpoints = new ConcurrentHashMap<>();
|
||||
|
||||
private volatile Collection<E> endpoints;
|
||||
|
||||
/**
|
||||
* Create a new {@link EndpointDiscoverer} instance.
|
||||
* @param applicationContext the source application context
|
||||
* @param parameterValueMapper the parameter value mapper
|
||||
* @param invokerAdvisors invoker advisors to apply
|
||||
* @param filters filters to apply
|
||||
*/
|
||||
public EndpointDiscoverer(ApplicationContext applicationContext,
|
||||
ParameterValueMapper parameterValueMapper,
|
||||
Collection<OperationInvokerAdvisor> invokerAdvisors,
|
||||
Collection<EndpointFilter<E>> filters) {
|
||||
Assert.notNull(applicationContext, "ApplicationContext must not be null");
|
||||
Assert.notNull(parameterValueMapper, "ParameterValueMapper must not be null");
|
||||
Assert.notNull(invokerAdvisors, "InvokerAdvisors must not be null");
|
||||
Assert.notNull(filters, "Filters must not be null");
|
||||
this.applicationContext = applicationContext;
|
||||
this.filters = Collections.unmodifiableCollection(filters);
|
||||
this.operationsFactory = getOperationsFactory(parameterValueMapper,
|
||||
invokerAdvisors);
|
||||
}
|
||||
|
||||
private DiscoveredOperationsFactory<O> getOperationsFactory(
|
||||
ParameterValueMapper parameterValueMapper,
|
||||
Collection<OperationInvokerAdvisor> invokerAdvisors) {
|
||||
return new DiscoveredOperationsFactory<O>(parameterValueMapper, invokerAdvisors) {
|
||||
|
||||
@Override
|
||||
protected O createOperation(String endpointId,
|
||||
DiscoveredOperationMethod operationMethod, OperationInvoker invoker) {
|
||||
return EndpointDiscoverer.this.createOperation(endpointId,
|
||||
operationMethod, invoker);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Collection<E> getEndpoints() {
|
||||
if (this.endpoints == null) {
|
||||
this.endpoints = discoverEndpoints();
|
||||
}
|
||||
return this.endpoints;
|
||||
}
|
||||
|
||||
private Collection<E> discoverEndpoints() {
|
||||
Collection<EndpointBean> endpointBeans = createEndpointBeans();
|
||||
addExtensionBeans(endpointBeans);
|
||||
return convertToEndpoints(endpointBeans);
|
||||
}
|
||||
|
||||
private Collection<EndpointBean> createEndpointBeans() {
|
||||
Map<String, EndpointBean> byId = new LinkedHashMap<>();
|
||||
String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(
|
||||
this.applicationContext, Endpoint.class);
|
||||
for (String beanName : beanNames) {
|
||||
EndpointBean endpointBean = createEndpointBean(beanName);
|
||||
EndpointBean previous = byId.putIfAbsent(endpointBean.getId(), endpointBean);
|
||||
Assert.state(previous == null,
|
||||
() -> "Found two endpoints with the id '" + endpointBean.getId()
|
||||
+ "': '" + endpointBean.getBeanName() + "' and '"
|
||||
+ previous.getBeanName() + "'");
|
||||
}
|
||||
return byId.values();
|
||||
}
|
||||
|
||||
private EndpointBean createEndpointBean(String beanName) {
|
||||
Object bean = this.applicationContext.getBean(beanName);
|
||||
return new EndpointBean(beanName, bean);
|
||||
}
|
||||
|
||||
private void addExtensionBeans(Collection<EndpointBean> endpointBeans) {
|
||||
Map<?, EndpointBean> byType = endpointBeans.stream()
|
||||
.collect(Collectors.toMap((bean) -> bean.getType(), (bean) -> bean));
|
||||
String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(
|
||||
this.applicationContext, EndpointExtension.class);
|
||||
for (String beanName : beanNames) {
|
||||
ExtensionBean extensionBean = createExtensionBean(beanName);
|
||||
EndpointBean endpointBean = byType.get(extensionBean.getEndpointType());
|
||||
Assert.state(endpointBean != null,
|
||||
() -> ("Invalid extension '" + extensionBean.getBeanName()
|
||||
+ "': no endpoint found with type '"
|
||||
+ extensionBean.getEndpointType().getName() + "'"));
|
||||
addExtensionBean(endpointBean, extensionBean);
|
||||
}
|
||||
}
|
||||
|
||||
private ExtensionBean createExtensionBean(String beanName) {
|
||||
Object bean = this.applicationContext.getBean(beanName);
|
||||
return new ExtensionBean(beanName, bean);
|
||||
}
|
||||
|
||||
private void addExtensionBean(EndpointBean endpointBean,
|
||||
ExtensionBean extensionBean) {
|
||||
if (isExtensionExposed(endpointBean, extensionBean)) {
|
||||
Assert.state(
|
||||
isEndpointExposed(endpointBean) || isEndpointFiltered(endpointBean),
|
||||
() -> "Endpoint bean '" + endpointBean.getBeanName()
|
||||
+ "' cannot support the extension bean '"
|
||||
+ extensionBean.getBeanName() + "'");
|
||||
endpointBean.addExtension(extensionBean);
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<E> convertToEndpoints(Collection<EndpointBean> endpointBeans) {
|
||||
Set<E> endpoints = new LinkedHashSet<>();
|
||||
for (EndpointBean endpointBean : endpointBeans) {
|
||||
if (isEndpointExposed(endpointBean)) {
|
||||
endpoints.add(convertToEndpoint(endpointBean));
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableSet(endpoints);
|
||||
}
|
||||
|
||||
private E convertToEndpoint(EndpointBean endpointBean) {
|
||||
MultiValueMap<OperationKey, O> indexed = new LinkedMultiValueMap<>();
|
||||
String id = endpointBean.getId();
|
||||
addOperations(indexed, id, endpointBean.getBean(), false);
|
||||
if (endpointBean.getExtensions().size() > 1) {
|
||||
String extensionBeans = endpointBean.getExtensions().stream()
|
||||
.map(ExtensionBean::getBeanName).collect(Collectors.joining(", "));
|
||||
throw new IllegalStateException(
|
||||
"Found multiple extensions for the endpoint bean "
|
||||
+ endpointBean.getBeanName() + " (" + extensionBeans + ")");
|
||||
}
|
||||
for (ExtensionBean extensionBean : endpointBean.getExtensions()) {
|
||||
addOperations(indexed, id, extensionBean.getBean(), true);
|
||||
}
|
||||
assertNoDuplicateOperations(endpointBean, indexed);
|
||||
List<O> operations = indexed.values().stream().map(this::getLast)
|
||||
.filter(Objects::nonNull).collect(Collectors.collectingAndThen(
|
||||
Collectors.toList(), Collections::unmodifiableList));
|
||||
return createEndpoint(id, endpointBean.isEnabledByDefault(), operations);
|
||||
}
|
||||
|
||||
private void addOperations(MultiValueMap<OperationKey, O> indexed, String id,
|
||||
Object target, boolean replaceLast) {
|
||||
Set<OperationKey> replacedLast = new HashSet<>();
|
||||
Collection<O> operations = this.operationsFactory.createOperations(id, target);
|
||||
for (O operation : operations) {
|
||||
OperationKey key = createOperationKey(operation);
|
||||
O last = getLast(indexed.get(key));
|
||||
if (replaceLast && replacedLast.add(key) && last != null) {
|
||||
indexed.get(key).remove(last);
|
||||
}
|
||||
indexed.add(key, operation);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T getLast(List<T> list) {
|
||||
return CollectionUtils.isEmpty(list) ? null : list.get(list.size() - 1);
|
||||
}
|
||||
|
||||
private void assertNoDuplicateOperations(EndpointBean endpointBean,
|
||||
MultiValueMap<OperationKey, O> indexed) {
|
||||
List<OperationKey> duplicates = indexed.entrySet().stream()
|
||||
.filter((entry) -> entry.getValue().size() > 1).map(Map.Entry::getKey)
|
||||
.collect(Collectors.toList());
|
||||
if (!duplicates.isEmpty()) {
|
||||
Set<ExtensionBean> extensions = endpointBean.getExtensions();
|
||||
String extensionBeanNames = extensions.stream()
|
||||
.map(ExtensionBean::getBeanName).collect(Collectors.joining(", "));
|
||||
throw new IllegalStateException(
|
||||
"Unable to map duplicate endpoint operations: "
|
||||
+ duplicates.toString() + " to " + endpointBean.getBeanName()
|
||||
+ (extensions.isEmpty() ? ""
|
||||
: " (" + extensionBeanNames + ")"));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isExtensionExposed(EndpointBean endpointBean,
|
||||
ExtensionBean extensionBean) {
|
||||
return isFilterMatch(extensionBean.getFilter(), endpointBean)
|
||||
&& isExtensionExposed(extensionBean.getBean());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an extension bean should be exposed. Subclasses can override this
|
||||
* method to provide additional logic.
|
||||
* @param extensionBean the extension bean
|
||||
* @return {@code true} if the extension is exposed
|
||||
*/
|
||||
protected boolean isExtensionExposed(Object extensionBean) {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isEndpointExposed(EndpointBean endpointBean) {
|
||||
return isFilterMatch(endpointBean.getFilter(), endpointBean)
|
||||
&& !isEndpointFiltered(endpointBean);
|
||||
}
|
||||
|
||||
private boolean isEndpointFiltered(EndpointBean endpointBean) {
|
||||
for (EndpointFilter<E> filter : this.filters) {
|
||||
if (!isFilterMatch(filter, endpointBean)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private boolean isFilterMatch(Class<?> filter, EndpointBean endpointBean) {
|
||||
if (filter == null) {
|
||||
return true;
|
||||
}
|
||||
E endpoint = getFilterEndpoint(endpointBean);
|
||||
Class<?> generic = ResolvableType.forClass(EndpointFilter.class, filter)
|
||||
.resolveGeneric(0);
|
||||
if (generic == null || generic.isInstance(endpoint)) {
|
||||
EndpointFilter<E> instance = (EndpointFilter<E>) BeanUtils
|
||||
.instantiateClass(filter);
|
||||
return isFilterMatch(instance, endpoint);
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
private boolean isFilterMatch(EndpointFilter<E> filter, EndpointBean endpointBean) {
|
||||
return isFilterMatch(filter, getFilterEndpoint(endpointBean));
|
||||
}
|
||||
|
||||
private boolean isFilterMatch(EndpointFilter<E> filter, E endpoint) {
|
||||
try {
|
||||
return filter.match(endpoint);
|
||||
}
|
||||
catch (ClassCastException ex) {
|
||||
String msg = ex.getMessage();
|
||||
if (msg == null || msg.startsWith(endpoint.getClass().getName())) {
|
||||
// Possibly a lambda-defined EndpointFilter which we could not resolve the
|
||||
// generic EndpointInfo type for
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Non-matching Endpoint for EndpointFilter: " + filter,
|
||||
ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
private E getFilterEndpoint(EndpointBean endpointBean) {
|
||||
E endpoint = this.filterEndpoints.get(endpointBean);
|
||||
if (endpoint == null) {
|
||||
endpoint = createEndpoint(endpointBean.getId(),
|
||||
endpointBean.isEnabledByDefault(), Collections.emptySet());
|
||||
this.filterEndpoints.put(endpointBean, endpoint);
|
||||
}
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Class<? extends E> getEndpointType() {
|
||||
return (Class<? extends E>) ResolvableType
|
||||
.forClass(EndpointDiscoverer.class, getClass()).resolveGeneric(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method called to create the {@link ExposableEndpoint endpoint}.
|
||||
* @param id the ID of the endpoint
|
||||
* @param enabledByDefault if the endpoint is enabled by default
|
||||
* @param operations the endpoint operations
|
||||
* @return a created endpoint (a {@link DiscoveredEndpoint} is recommended)
|
||||
*/
|
||||
protected abstract E createEndpoint(String id, boolean enabledByDefault,
|
||||
Collection<O> operations);
|
||||
|
||||
/**
|
||||
* Factory method to create an {@link Operation endpoint operation}.
|
||||
* @param endpointId the endpoint id
|
||||
* @param operationMethod the operation method
|
||||
* @param invoker the invoker to use
|
||||
* @return a created operation
|
||||
*/
|
||||
protected abstract O createOperation(String endpointId,
|
||||
DiscoveredOperationMethod operationMethod, OperationInvoker invoker);
|
||||
|
||||
/**
|
||||
* Create a {@link OperationKey} for the given operation.
|
||||
* @param operation the source operation
|
||||
* @return the operation key
|
||||
*/
|
||||
protected abstract OperationKey createOperationKey(O operation);
|
||||
|
||||
/**
|
||||
* A key generated for an {@link Operation} based on specific criteria from the actual
|
||||
* operation implementation.
|
||||
*/
|
||||
protected static final class OperationKey {
|
||||
|
||||
private final Object key;
|
||||
|
||||
private final Supplier<String> description;
|
||||
|
||||
/**
|
||||
* Create a new {@link OperationKey} instance.
|
||||
* @param key the underlying key for the operation
|
||||
* @param description a human readable description of the key
|
||||
*/
|
||||
public OperationKey(Object key, Supplier<String> description) {
|
||||
Assert.notNull(key, "Key must not be null");
|
||||
Assert.notNull(description, "Description must not be null");
|
||||
this.key = key;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.key.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
return this.key.equals(((OperationKey) obj).key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.description.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about an {@link Endpoint @Endpoint} bean.
|
||||
*/
|
||||
private static class EndpointBean {
|
||||
|
||||
private final String beanName;
|
||||
|
||||
private final Object bean;
|
||||
|
||||
private final String id;
|
||||
|
||||
private boolean enabledByDefault;
|
||||
|
||||
private final Class<?> filter;
|
||||
|
||||
private Set<ExtensionBean> extensions = new LinkedHashSet<>();
|
||||
|
||||
EndpointBean(String beanName, Object bean) {
|
||||
AnnotationAttributes attributes = AnnotatedElementUtils
|
||||
.findMergedAnnotationAttributes(bean.getClass(), Endpoint.class, true,
|
||||
true);
|
||||
this.beanName = beanName;
|
||||
this.bean = bean;
|
||||
this.id = attributes.getString("id");
|
||||
this.enabledByDefault = (Boolean) attributes.get("enableByDefault");
|
||||
this.filter = getFilter(this.bean.getClass());
|
||||
Assert.state(StringUtils.hasText(this.id),
|
||||
"No @Endpoint id attribute specified for "
|
||||
+ bean.getClass().getName());
|
||||
}
|
||||
|
||||
public void addExtension(ExtensionBean extensionBean) {
|
||||
this.extensions.add(extensionBean);
|
||||
}
|
||||
|
||||
public Set<ExtensionBean> getExtensions() {
|
||||
return this.extensions;
|
||||
}
|
||||
|
||||
private Class<?> getFilter(Class<?> type) {
|
||||
AnnotationAttributes attributes = AnnotatedElementUtils
|
||||
.getMergedAnnotationAttributes(type, FilteredEndpoint.class);
|
||||
if (attributes == null) {
|
||||
return null;
|
||||
}
|
||||
return attributes.getClass("value");
|
||||
}
|
||||
|
||||
public String getBeanName() {
|
||||
return this.beanName;
|
||||
}
|
||||
|
||||
public Object getBean() {
|
||||
return this.bean;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public Class<?> getType() {
|
||||
return this.bean.getClass();
|
||||
}
|
||||
|
||||
public boolean isEnabledByDefault() {
|
||||
return this.enabledByDefault;
|
||||
}
|
||||
|
||||
public Class<?> getFilter() {
|
||||
return this.filter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about an {@link EndpointExtension EndpointExtension} bean.
|
||||
*/
|
||||
private static class ExtensionBean {
|
||||
|
||||
private final String beanName;
|
||||
|
||||
private final Object bean;
|
||||
|
||||
private final Class<?> endpointType;
|
||||
|
||||
private final Class<?> filter;
|
||||
|
||||
ExtensionBean(String beanName, Object bean) {
|
||||
AnnotationAttributes attributes = AnnotatedElementUtils
|
||||
.getMergedAnnotationAttributes(bean.getClass(),
|
||||
EndpointExtension.class);
|
||||
this.beanName = beanName;
|
||||
this.bean = bean;
|
||||
this.endpointType = attributes.getClass("endpoint");
|
||||
this.filter = attributes.getClass("filter");
|
||||
Assert.state(!this.endpointType.equals(Void.class), () -> "Extension "
|
||||
+ this.endpointType.getName() + " does not specify an endpoint");
|
||||
}
|
||||
|
||||
public String getBeanName() {
|
||||
return this.beanName;
|
||||
}
|
||||
|
||||
public Object getBean() {
|
||||
return this.bean;
|
||||
}
|
||||
|
||||
public Class<?> getEndpointType() {
|
||||
return this.endpointType;
|
||||
}
|
||||
|
||||
public Class<?> getFilter() {
|
||||
return this.filter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -41,6 +41,7 @@ import org.springframework.boot.actuate.endpoint.EndpointFilter;
|
||||
* } </pre>
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
* @see DiscovererEndpointFilter
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
|
@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.annotation;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.Operation;
|
||||
import org.springframework.boot.actuate.endpoint.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInfo;
|
||||
|
||||
/**
|
||||
* Factory to creates an {@link Operation} for an annotated method on an
|
||||
* {@link Endpoint @Endpoint}.
|
||||
*
|
||||
* @param <T> the {@link Operation} type
|
||||
* @author Andy Wilkinson
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface OperationFactory<T extends Operation> {
|
||||
|
||||
/**
|
||||
* Creates an {@link Operation} for an operation on an endpoint.
|
||||
* @param endpointId the id of the endpoint
|
||||
* @param target the target that implements the operation
|
||||
* @param methodInfo the method on the bean that implements the operation
|
||||
* @param invoker the invoker that should be used for the operation
|
||||
* @return the operation info that describes the operation
|
||||
*/
|
||||
T createOperation(String endpointId, OperationMethodInfo methodInfo, Object target,
|
||||
OperationInvoker invoker);
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.reflect;
|
||||
package org.springframework.boot.actuate.endpoint.invoke;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@ -25,24 +25,25 @@ import org.springframework.util.StringUtils;
|
||||
* parameters.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class ParametersMissingException extends RuntimeException {
|
||||
public class MissingParametersException extends RuntimeException {
|
||||
|
||||
private final Set<String> parameters;
|
||||
private final Set<OperationParameter> missingParameters;
|
||||
|
||||
public ParametersMissingException(Set<String> parameters) {
|
||||
public MissingParametersException(Set<OperationParameter> missingParameters) {
|
||||
super("Failed to invoke operation because the following required "
|
||||
+ "parameters were missing: "
|
||||
+ StringUtils.collectionToCommaDelimitedString(parameters));
|
||||
this.parameters = parameters;
|
||||
+ StringUtils.collectionToCommaDelimitedString(missingParameters));
|
||||
this.missingParameters = missingParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parameters that were missing.
|
||||
* @return the parameters
|
||||
*/
|
||||
public Set<String> getParameters() {
|
||||
return this.parameters;
|
||||
public Set<OperationParameter> getMissingParameters() {
|
||||
return this.missingParameters;
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -14,14 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
package org.springframework.boot.actuate.endpoint.invoke;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An {@code OperationInvoker} is used to invoke an operation on an endpoint.
|
||||
* Interface to perform an operation invocation.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@ -31,7 +32,8 @@ public interface OperationInvoker {
|
||||
* Invoke the underlying operation using the given {@code arguments}.
|
||||
* @param arguments the arguments to pass to the operation
|
||||
* @return the result of the operation, may be {@code null}
|
||||
* @throws MissingParametersException if parameters are missing
|
||||
*/
|
||||
Object invoke(Map<String, Object> arguments);
|
||||
Object invoke(Map<String, Object> arguments) throws MissingParametersException;
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -14,28 +14,28 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.reflect;
|
||||
package org.springframework.boot.actuate.endpoint.invoke;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||
|
||||
/**
|
||||
* Allows additional functionality to be applied to an {@link OperationInvoker} being used
|
||||
* to invoke a {@link OperationMethodInfo operation method}.
|
||||
* Allows additional functionality to be applied to an {@link OperationInvoker}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface OperationMethodInvokerAdvisor {
|
||||
public interface OperationInvokerAdvisor {
|
||||
|
||||
/**
|
||||
* Apply additional functionality to the given invoker.
|
||||
* @param endpointId the endpoint ID
|
||||
* @param methodInfo the method information
|
||||
* @param operationType the operation type
|
||||
* @param parameters the operation parameters
|
||||
* @param invoker the invoker to advise
|
||||
* @return an potentially new operation invoker with support for additional features.
|
||||
* @return an potentially new operation invoker with support for additional features
|
||||
*/
|
||||
OperationInvoker apply(String endpointId, OperationMethodInfo methodInfo,
|
||||
OperationInvoker invoker);
|
||||
OperationInvoker apply(String endpointId, OperationType operationType,
|
||||
OperationParameters parameters, OperationInvoker invoker);
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* A single operation parameter.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public interface OperationParameter {
|
||||
|
||||
/**
|
||||
* Returns the parameter name.
|
||||
* @return the name
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Returns the parameter type.
|
||||
* @return the type
|
||||
*/
|
||||
Class<?> getType();
|
||||
|
||||
/**
|
||||
* Return if the parameter accepts null values.
|
||||
* @return if the parameter is nullable
|
||||
*/
|
||||
boolean isNullable();
|
||||
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A collection of {@link OperationParameter operation parameters}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public interface OperationParameters extends Iterable<OperationParameter> {
|
||||
|
||||
/**
|
||||
* Return {@code true} if there is at least one parameter.
|
||||
* @return if there are parameters
|
||||
*/
|
||||
default boolean hasParameters() {
|
||||
return getParameterCount() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the total number of parameters.
|
||||
* @return the total number of parameters
|
||||
*/
|
||||
int getParameterCount();
|
||||
|
||||
/**
|
||||
* Return the parameter at the specified index.
|
||||
* @param index the parameter index
|
||||
* @return the paramter
|
||||
*/
|
||||
OperationParameter get(int index);
|
||||
|
||||
/**
|
||||
* Return a stream of the contained paramteres.
|
||||
* @return a stream of the parameters
|
||||
*/
|
||||
Stream<OperationParameter> stream();
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -14,50 +14,50 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.reflect;
|
||||
package org.springframework.boot.actuate.endpoint.invoke;
|
||||
|
||||
/**
|
||||
* A {@code ParameterMappingException} is thrown when a failure occurs during
|
||||
* {@link ParameterMapper#mapParameter(Object, Class) operation parameter mapping}.
|
||||
* {@link ParameterValueMapper#mapParameterValue operation parameter mapping}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class ParameterMappingException extends RuntimeException {
|
||||
|
||||
private final Object input;
|
||||
private final OperationParameter parameter;
|
||||
|
||||
private final Class<?> type;
|
||||
private final Object value;
|
||||
|
||||
/**
|
||||
* Creates a new {@code ParameterMappingException} for a failure that occurred when
|
||||
* trying to map the given {@code input} to the given {@code type}.
|
||||
*
|
||||
* @param input the input that was being mapped
|
||||
* @param type the type that was being mapped to
|
||||
* @param parameter the parameter being mapping
|
||||
* @param value the value being mapped
|
||||
* @param cause the cause of the mapping failure
|
||||
*/
|
||||
public ParameterMappingException(Object input, Class<?> type, Throwable cause) {
|
||||
super("Failed to map " + input + " of type " + input.getClass() + " to type "
|
||||
+ type, cause);
|
||||
this.input = input;
|
||||
this.type = type;
|
||||
public ParameterMappingException(OperationParameter parameter, Object value,
|
||||
Throwable cause) {
|
||||
super("Failed to map " + value + " of type " + value.getClass() + " to "
|
||||
+ parameter, cause);
|
||||
this.parameter = parameter;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the input that was to be mapped.
|
||||
* @return the input
|
||||
* Return the parameter being mapped.
|
||||
* @return the parameter
|
||||
*/
|
||||
public Object getInput() {
|
||||
return this.input;
|
||||
public OperationParameter getParameter() {
|
||||
return this.parameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type to be mapped to.
|
||||
* @return the type
|
||||
* Return the value being mapped.
|
||||
* @return the value
|
||||
*/
|
||||
public Class<?> getType() {
|
||||
return this.type;
|
||||
public Object getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -14,25 +14,25 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.reflect;
|
||||
package org.springframework.boot.actuate.endpoint.invoke;
|
||||
|
||||
/**
|
||||
* Maps parameters to the required type when invoking an endpoint.
|
||||
* Maps parameter values to the required type when invoking an endpoint.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ParameterMapper {
|
||||
public interface ParameterValueMapper {
|
||||
|
||||
/**
|
||||
* Map the specified {@code input} parameter to the given {@code parameterType}.
|
||||
* @param parameter the parameter to map
|
||||
* @param value a parameter value
|
||||
* @param type the required type of the parameter
|
||||
* @return a value suitable for that parameter
|
||||
* @param <T> the actual type of the parameter
|
||||
* @throws ParameterMappingException when a mapping failure occurs
|
||||
*/
|
||||
<T> T mapParameter(Object value, Class<T> type);
|
||||
Object mapParameterValue(OperationParameter parameter, Object value)
|
||||
throws ParameterMappingException;
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -14,49 +14,56 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.convert;
|
||||
package org.springframework.boot.actuate.endpoint.invoke.convert;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.ParameterMappingException;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationParameter;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.ParameterMappingException;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
||||
import org.springframework.boot.context.properties.bind.convert.BinderConversionService;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ParameterMapper} that uses a {@link ConversionService} to map parameter values
|
||||
* if necessary.
|
||||
* {@link ParameterValueMapper} backed by a {@link ConversionService}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class ConversionServiceParameterMapper implements ParameterMapper {
|
||||
public class ConversionServiceParameterValueMapper implements ParameterValueMapper {
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
public ConversionServiceParameterMapper() {
|
||||
this(createDefaultConversionService());
|
||||
/**
|
||||
* Create a new {@link ConversionServiceParameterValueMapper} instance.
|
||||
*/
|
||||
public ConversionServiceParameterValueMapper() {
|
||||
this(createConversionService());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance with the {@link ConversionService} to use.
|
||||
* Create a new {@link ConversionServiceParameterValueMapper} instance backed by a
|
||||
* specific conversion service.
|
||||
* @param conversionService the conversion service
|
||||
*/
|
||||
public ConversionServiceParameterMapper(ConversionService conversionService) {
|
||||
public ConversionServiceParameterValueMapper(ConversionService conversionService) {
|
||||
Assert.notNull(conversionService, "ConversionService must not be null");
|
||||
this.conversionService = new BinderConversionService(conversionService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T mapParameter(Object input, Class<T> parameterType) {
|
||||
public Object mapParameterValue(OperationParameter parameter, Object value)
|
||||
throws ParameterMappingException {
|
||||
try {
|
||||
return this.conversionService.convert(input, parameterType);
|
||||
return this.conversionService.convert(value, parameter.getType());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new ParameterMappingException(input, parameterType, ex);
|
||||
throw new ParameterMappingException(parameter, value, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static ConversionService createDefaultConversionService() {
|
||||
private static ConversionService createConversionService() {
|
||||
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
|
||||
IsoOffsetDateTimeConverter.registerConverter(conversionService);
|
||||
return conversionService;
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.convert;
|
||||
package org.springframework.boot.actuate.endpoint.invoke.convert;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -17,4 +17,4 @@
|
||||
/**
|
||||
* Converter support for actuator endpoints.
|
||||
*/
|
||||
package org.springframework.boot.actuate.endpoint.convert;
|
||||
package org.springframework.boot.actuate.endpoint.invoke.convert;
|
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interfaces and classes relating to invoking operation methods.
|
||||
*/
|
||||
package org.springframework.boot.actuate.endpoint.invoke;
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -14,17 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.reflect;
|
||||
package org.springframework.boot.actuate.endpoint.invoke.reflect;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationParameters;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@ -34,7 +31,7 @@ import org.springframework.util.Assert;
|
||||
* @since 2.0.0
|
||||
* @see ReflectiveOperationInvoker
|
||||
*/
|
||||
public final class OperationMethodInfo {
|
||||
public class OperationMethod {
|
||||
|
||||
private static final ParameterNameDiscoverer DEFAULT_PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
|
||||
|
||||
@ -42,18 +39,20 @@ public final class OperationMethodInfo {
|
||||
|
||||
private final OperationType operationType;
|
||||
|
||||
private final AnnotationAttributes annotationAttributes;
|
||||
private final OperationParameters operationParameters;
|
||||
|
||||
private final ParameterNameDiscoverer parameterNameDiscoverer = DEFAULT_PARAMETER_NAME_DISCOVERER;
|
||||
|
||||
public OperationMethodInfo(Method method, OperationType operationType,
|
||||
AnnotationAttributes annotationAttributes) {
|
||||
/**
|
||||
* Create a new {@link OperationMethod} instance.
|
||||
* @param method the source method
|
||||
* @param operationType the operation type
|
||||
*/
|
||||
public OperationMethod(Method method, OperationType operationType) {
|
||||
Assert.notNull(method, "Method must not be null");
|
||||
Assert.notNull(operationType, "Operation Type must not be null");
|
||||
Assert.notNull(annotationAttributes, "Annotation Attributes must not be null");
|
||||
Assert.notNull(operationType, "OperationType must not be null");
|
||||
this.method = method;
|
||||
this.operationType = operationType;
|
||||
this.annotationAttributes = annotationAttributes;
|
||||
this.operationParameters = new OperationMethodParameters(method,
|
||||
DEFAULT_PARAMETER_NAME_DISCOVERER);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,27 +72,17 @@ public final class OperationMethodInfo {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mime type that the operation produces.
|
||||
* @return the produced mime type
|
||||
* Return the operation parameters.
|
||||
* @return the operation parameters
|
||||
*/
|
||||
public String[] getProduces() {
|
||||
return this.annotationAttributes.getStringArray("produces");
|
||||
public OperationParameters getParameters() {
|
||||
return this.operationParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a map of method parameters with the key being the discovered parameter name.
|
||||
* @return the method parameters
|
||||
*/
|
||||
public Map<String, Parameter> getParameters() {
|
||||
Parameter[] parameters = this.method.getParameters();
|
||||
String[] names = this.parameterNameDiscoverer.getParameterNames(this.method);
|
||||
Assert.state(names != null,
|
||||
"Failed to extract parameter names for " + this.method);
|
||||
Map<String, Parameter> result = new LinkedHashMap<>();
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
result.put(names[i], parameters[i]);
|
||||
}
|
||||
return result;
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Operation " + this.operationType.name().toLowerCase() + " method "
|
||||
+ this.method;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.reflect;
|
||||
|
||||
import java.lang.reflect.Parameter;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationParameter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* {@link OperationParameter} created from an {@link OperationMethod}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class OperationMethodParameter implements OperationParameter {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final Parameter parameter;
|
||||
|
||||
/**
|
||||
* Create a new {@link OperationMethodParameter} instance.
|
||||
* @param name the parameter name
|
||||
* @param parameter the parameter
|
||||
*/
|
||||
OperationMethodParameter(String name, Parameter parameter) {
|
||||
this.name = name;
|
||||
this.parameter = parameter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getType() {
|
||||
return this.parameter.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNullable() {
|
||||
return !ObjectUtils.isEmpty(this.parameter.getAnnotationsByType(Nullable.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name + " of type " + this.parameter.getType().getName();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.reflect;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationParameter;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationParameters;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link OperationParameters} created from an {@link OperationMethod}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class OperationMethodParameters implements OperationParameters {
|
||||
|
||||
private final List<OperationParameter> operationParameters;
|
||||
|
||||
/**
|
||||
* Create a new {@link OperationMethodParameters} instance.
|
||||
* @param method the source method
|
||||
* @param parameterNameDiscoverer the parameter name discoverer
|
||||
*/
|
||||
OperationMethodParameters(Method method,
|
||||
ParameterNameDiscoverer parameterNameDiscoverer) {
|
||||
Assert.notNull(method, "Method must not be null");
|
||||
Assert.notNull(parameterNameDiscoverer,
|
||||
"ParameterNameDiscoverer must not be null");
|
||||
String[] paramterNames = parameterNameDiscoverer.getParameterNames(method);
|
||||
Parameter[] parameters = method.getParameters();
|
||||
Assert.state(paramterNames != null,
|
||||
"Failed to extract parameter names for " + method);
|
||||
this.operationParameters = getOperationParameters(parameters, paramterNames);
|
||||
}
|
||||
|
||||
private List<OperationParameter> getOperationParameters(Parameter[] parameters,
|
||||
String[] names) {
|
||||
List<OperationParameter> operationParameters = new ArrayList<>(parameters.length);
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
operationParameters
|
||||
.add(new OperationMethodParameter(names[i], parameters[i]));
|
||||
}
|
||||
return Collections.unmodifiableList(operationParameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getParameterCount() {
|
||||
return this.operationParameters.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OperationParameter get(int index) {
|
||||
return this.operationParameters.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<OperationParameter> iterator() {
|
||||
return this.operationParameters.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<OperationParameter> stream() {
|
||||
return this.operationParameters.stream();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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.reflect;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.invoke.MissingParametersException;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationParameter;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* An {@code OperationInvoker} that invokes an operation using reflection.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class ReflectiveOperationInvoker implements OperationInvoker {
|
||||
|
||||
private final Object target;
|
||||
|
||||
private final OperationMethod operationMethod;
|
||||
|
||||
private final ParameterValueMapper parameterValueMapper;
|
||||
|
||||
/**
|
||||
* Creates a new {code ReflectiveOperationInvoker} that will invoke the given
|
||||
* {@code method} on the given {@code target}. The given {@code parameterMapper} will
|
||||
* be used to map parameters to the required types and the given
|
||||
* {@code parameterNameMapper} will be used map parameters by name.
|
||||
* @param target the target of the reflective call
|
||||
* @param operationMethod the method info
|
||||
* @param parameterValueMapper the parameter mapper
|
||||
*/
|
||||
public ReflectiveOperationInvoker(Object target, OperationMethod operationMethod,
|
||||
ParameterValueMapper parameterValueMapper) {
|
||||
Assert.notNull(target, "Target must not be null");
|
||||
Assert.notNull(operationMethod, "OperationMethod must not be null");
|
||||
Assert.notNull(parameterValueMapper, "ParameterValueMapper must not be null");
|
||||
ReflectionUtils.makeAccessible(operationMethod.getMethod());
|
||||
this.target = target;
|
||||
this.operationMethod = operationMethod;
|
||||
this.parameterValueMapper = parameterValueMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Map<String, Object> arguments) {
|
||||
validateRequiredParameters(arguments);
|
||||
Method method = this.operationMethod.getMethod();
|
||||
Object[] resolvedArguments = resolveArguments(arguments);
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
return ReflectionUtils.invokeMethod(method, this.target, resolvedArguments);
|
||||
}
|
||||
|
||||
private void validateRequiredParameters(Map<String, Object> arguments) {
|
||||
Set<OperationParameter> missing = this.operationMethod.getParameters().stream()
|
||||
.filter((parameter) -> isMissing(arguments, parameter))
|
||||
.collect(Collectors.toSet());
|
||||
if (!missing.isEmpty()) {
|
||||
throw new MissingParametersException(missing);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMissing(Map<String, Object> arguments,
|
||||
OperationParameter parameter) {
|
||||
if (parameter.isNullable()) {
|
||||
return false;
|
||||
}
|
||||
return arguments.get(parameter.getName()) == null;
|
||||
}
|
||||
|
||||
private Object[] resolveArguments(Map<String, Object> arguments) {
|
||||
return this.operationMethod.getParameters().stream()
|
||||
.map((parameter) -> resolveArgument(parameter, arguments)).toArray();
|
||||
}
|
||||
|
||||
private Object resolveArgument(OperationParameter parameter,
|
||||
Map<String, Object> arguments) {
|
||||
Object value = arguments.get(parameter.getName());
|
||||
return this.parameterValueMapper.mapParameterValue(parameter, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("target", this.target)
|
||||
.append("method", this.operationMethod).toString();
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -17,4 +17,4 @@
|
||||
/**
|
||||
* Endpoint reflection support.
|
||||
*/
|
||||
package org.springframework.boot.actuate.endpoint.reflect;
|
||||
package org.springframework.boot.actuate.endpoint.invoke.reflect;
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -14,11 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.cache;
|
||||
package org.springframework.boot.actuate.endpoint.invoker.cache;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@ -30,7 +30,7 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class CachingOperationInvoker implements OperationInvoker {
|
||||
|
||||
private final OperationInvoker target;
|
||||
private final OperationInvoker invoker;
|
||||
|
||||
private final long timeToLive;
|
||||
|
||||
@ -39,12 +39,12 @@ public class CachingOperationInvoker implements OperationInvoker {
|
||||
/**
|
||||
* Create a new instance with the target {@link OperationInvoker} to use to compute
|
||||
* the response and the time to live for the cache.
|
||||
* @param target the {@link OperationInvoker} this instance wraps
|
||||
* @param invoker the {@link OperationInvoker} this instance wraps
|
||||
* @param timeToLive the maximum time in milliseconds that a response can be cached
|
||||
*/
|
||||
public CachingOperationInvoker(OperationInvoker target, long timeToLive) {
|
||||
Assert.state(timeToLive > 0, "TimeToLive must be strictly positive");
|
||||
this.target = target;
|
||||
CachingOperationInvoker(OperationInvoker invoker, long timeToLive) {
|
||||
Assert.isTrue(timeToLive > 0, "TimeToLive must be strictly positive");
|
||||
this.invoker = invoker;
|
||||
this.timeToLive = timeToLive;
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ public class CachingOperationInvoker implements OperationInvoker {
|
||||
long accessTime = System.currentTimeMillis();
|
||||
CachedResponse cached = this.cachedResponse;
|
||||
if (cached == null || cached.isStale(accessTime, this.timeToLive)) {
|
||||
Object response = this.target.invoke(arguments);
|
||||
Object response = this.invoker.invoke(arguments);
|
||||
this.cachedResponse = new CachedResponse(response, accessTime);
|
||||
return response;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -14,23 +14,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.cache;
|
||||
package org.springframework.boot.actuate.endpoint.invoker.cache;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInfo;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInvokerAdvisor;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationParameters;
|
||||
|
||||
/**
|
||||
* {@link OperationMethodInvokerAdvisor} to optionally wrap an {@link OperationInvoker}
|
||||
* with a {@link CachingOperationInvoker}.
|
||||
* {@link OperationInvokerAdvisor} to optionally provide result caching support.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class CachingOperationInvokerAdvisor implements OperationMethodInvokerAdvisor {
|
||||
public class CachingOperationInvokerAdvisor implements OperationInvokerAdvisor {
|
||||
|
||||
private final Function<String, Long> endpointIdTimeToLive;
|
||||
|
||||
@ -39,10 +38,9 @@ public class CachingOperationInvokerAdvisor implements OperationMethodInvokerAdv
|
||||
}
|
||||
|
||||
@Override
|
||||
public OperationInvoker apply(String endpointId, OperationMethodInfo methodInfo,
|
||||
OperationInvoker invoker) {
|
||||
if (methodInfo.getOperationType() == OperationType.READ
|
||||
&& methodInfo.getParameters().isEmpty()) {
|
||||
public OperationInvoker apply(String endpointId, OperationType operationType,
|
||||
OperationParameters parameters, OperationInvoker invoker) {
|
||||
if (operationType == OperationType.READ && !parameters.hasParameters()) {
|
||||
Long timeToLive = this.endpointIdTimeToLive.apply(endpointId);
|
||||
if (timeToLive != null && timeToLive > 0) {
|
||||
return new CachingOperationInvoker(invoker, timeToLive);
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -17,4 +17,4 @@
|
||||
/**
|
||||
* Caching support for actuator endpoints.
|
||||
*/
|
||||
package org.springframework.boot.actuate.endpoint.cache;
|
||||
package org.springframework.boot.actuate.endpoint.invoker.cache;
|
@ -16,10 +16,9 @@
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.jmx;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.management.Attribute;
|
||||
import javax.management.AttributeList;
|
||||
@ -32,76 +31,87 @@ import javax.management.ReflectionException;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.ParameterMappingException;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.ParametersMissingException;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.MissingParametersException;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.ParameterMappingException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* A {@link DynamicMBean} that invokes operations on an {@link EndpointInfo endpoint}.
|
||||
* Adapter to expose a {@link ExposableJmxEndpoint JMX endpoint} as a
|
||||
* {@link DynamicMBean}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
* @see EndpointMBeanInfoAssembler
|
||||
*/
|
||||
public class EndpointMBean implements DynamicMBean {
|
||||
|
||||
private static final boolean REACTOR_PRESENT = ClassUtils.isPresent(
|
||||
"reactor.core.publisher.Mono", EndpointMBean.class.getClassLoader());
|
||||
|
||||
private final Function<Object, Object> operationResponseConverter;
|
||||
private final JmxOperationResponseMapper responseMapper;
|
||||
|
||||
private final EndpointMBeanInfo endpointInfo;
|
||||
private final ExposableJmxEndpoint endpoint;
|
||||
|
||||
EndpointMBean(Function<Object, Object> operationResponseConverter,
|
||||
EndpointMBeanInfo endpointInfo) {
|
||||
this.operationResponseConverter = operationResponseConverter;
|
||||
this.endpointInfo = endpointInfo;
|
||||
private final MBeanInfo info;
|
||||
|
||||
private final Map<String, JmxOperation> operations;
|
||||
|
||||
EndpointMBean(JmxOperationResponseMapper responseMapper,
|
||||
ExposableJmxEndpoint endpoint) {
|
||||
Assert.notNull(responseMapper, "ResponseMapper must not be null");
|
||||
Assert.notNull(endpoint, "Endpoint must not be null");
|
||||
this.responseMapper = responseMapper;
|
||||
this.endpoint = endpoint;
|
||||
this.info = new MBeanInfoFactory(responseMapper).getMBeanInfo(endpoint);
|
||||
this.operations = getOperations(endpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the id of the related endpoint.
|
||||
* @return the endpoint id
|
||||
*/
|
||||
public String getEndpointId() {
|
||||
return this.endpointInfo.getEndpointId();
|
||||
private Map<String, JmxOperation> getOperations(ExposableJmxEndpoint endpoint) {
|
||||
Map<String, JmxOperation> operations = new HashMap<>();
|
||||
endpoint.getOperations()
|
||||
.forEach((operation) -> operations.put(operation.getName(), operation));
|
||||
return Collections.unmodifiableMap(operations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MBeanInfo getMBeanInfo() {
|
||||
return this.endpointInfo.getMbeanInfo();
|
||||
return this.info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(String actionName, Object[] params, String[] signature)
|
||||
throws MBeanException, ReflectionException {
|
||||
JmxOperation operation = this.endpointInfo.getOperations().get(actionName);
|
||||
if (operation != null) {
|
||||
Map<String, Object> arguments = getArguments(params,
|
||||
operation.getParameters());
|
||||
try {
|
||||
Object result = operation.getInvoker().invoke(arguments);
|
||||
if (REACTOR_PRESENT) {
|
||||
result = ReactiveHandler.handle(result);
|
||||
}
|
||||
return this.operationResponseConverter.apply(result);
|
||||
}
|
||||
catch (ParametersMissingException | ParameterMappingException ex) {
|
||||
throw new IllegalArgumentException(ex.getMessage());
|
||||
}
|
||||
|
||||
JmxOperation operation = this.operations.get(actionName);
|
||||
if (operation == null) {
|
||||
String message = "Endpoint with id '" + this.endpoint.getId()
|
||||
+ "' has no operation named " + actionName;
|
||||
throw new ReflectionException(new IllegalArgumentException(message), message);
|
||||
}
|
||||
throw new ReflectionException(new IllegalArgumentException(
|
||||
String.format("Endpoint with id '%s' has no operation named %s",
|
||||
this.endpointInfo.getEndpointId(), actionName)));
|
||||
return invoke(operation, params);
|
||||
}
|
||||
|
||||
private Map<String, Object> getArguments(Object[] params,
|
||||
List<JmxEndpointOperationParameterInfo> parameters) {
|
||||
private Object invoke(JmxOperation operation, Object[] params) {
|
||||
try {
|
||||
String[] parameterNames = operation.getParameters().stream()
|
||||
.map(JmxOperationParameter::getName).toArray(String[]::new);
|
||||
Map<String, Object> arguments = getArguments(parameterNames, params);
|
||||
Object result = operation.invoke(arguments);
|
||||
if (REACTOR_PRESENT) {
|
||||
result = ReactiveHandler.handle(result);
|
||||
}
|
||||
return this.responseMapper.mapResponse(result);
|
||||
}
|
||||
catch (MissingParametersException | ParameterMappingException ex) {
|
||||
throw new IllegalArgumentException(ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> getArguments(String[] parameterNames, Object[] params) {
|
||||
Map<String, Object> arguments = new HashMap<>();
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
arguments.put(parameters.get(i).getName(), params[i]);
|
||||
arguments.put(parameterNames[i], params[i]);
|
||||
}
|
||||
return arguments;
|
||||
}
|
||||
@ -109,13 +119,13 @@ public class EndpointMBean implements DynamicMBean {
|
||||
@Override
|
||||
public Object getAttribute(String attribute)
|
||||
throws AttributeNotFoundException, MBeanException, ReflectionException {
|
||||
throw new AttributeNotFoundException();
|
||||
throw new AttributeNotFoundException("EndpointMBeans do not support attributes");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(Attribute attribute) throws AttributeNotFoundException,
|
||||
InvalidAttributeValueException, MBeanException, ReflectionException {
|
||||
throw new AttributeNotFoundException();
|
||||
throw new AttributeNotFoundException("EndpointMBeans do not support attributes");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.jmx;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.management.MBeanInfo;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.Operation;
|
||||
|
||||
/**
|
||||
* The {@link MBeanInfo} for a particular {@link EndpointInfo endpoint}. Maps operation
|
||||
* names to an {@link Operation}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public final class EndpointMBeanInfo {
|
||||
|
||||
private final String endpointId;
|
||||
|
||||
private final MBeanInfo mBeanInfo;
|
||||
|
||||
private final Map<String, JmxOperation> operations;
|
||||
|
||||
public EndpointMBeanInfo(String endpointId, MBeanInfo mBeanInfo,
|
||||
Map<String, JmxOperation> operations) {
|
||||
this.endpointId = endpointId;
|
||||
this.mBeanInfo = mBeanInfo;
|
||||
this.operations = operations;
|
||||
}
|
||||
|
||||
public String getEndpointId() {
|
||||
return this.endpointId;
|
||||
}
|
||||
|
||||
public MBeanInfo getMbeanInfo() {
|
||||
return this.mBeanInfo;
|
||||
}
|
||||
|
||||
public Map<String, JmxOperation> getOperations() {
|
||||
return this.operations;
|
||||
}
|
||||
|
||||
}
|
@ -1,125 +0,0 @@
|
||||
/*
|
||||
* 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.jmx;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.management.MBeanInfo;
|
||||
import javax.management.MBeanOperationInfo;
|
||||
import javax.management.MBeanParameterInfo;
|
||||
import javax.management.modelmbean.ModelMBeanAttributeInfo;
|
||||
import javax.management.modelmbean.ModelMBeanConstructorInfo;
|
||||
import javax.management.modelmbean.ModelMBeanInfoSupport;
|
||||
import javax.management.modelmbean.ModelMBeanNotificationInfo;
|
||||
import javax.management.modelmbean.ModelMBeanOperationInfo;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||
|
||||
/**
|
||||
* Gathers the management operations of a particular {@link EndpointInfo endpoint}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class EndpointMBeanInfoAssembler {
|
||||
|
||||
private final JmxOperationResponseMapper responseMapper;
|
||||
|
||||
EndpointMBeanInfoAssembler(JmxOperationResponseMapper responseMapper) {
|
||||
this.responseMapper = responseMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link EndpointMBeanInfo} for the specified {@link EndpointInfo
|
||||
* endpoint}.
|
||||
* @param endpointInfo the endpoint to handle
|
||||
* @return the mbean info for the endpoint
|
||||
*/
|
||||
EndpointMBeanInfo createEndpointMBeanInfo(EndpointInfo<JmxOperation> endpointInfo) {
|
||||
Map<String, OperationInfos> operationsMapping = getOperationInfo(endpointInfo);
|
||||
ModelMBeanOperationInfo[] operationsMBeanInfo = operationsMapping.values()
|
||||
.stream().map((t) -> t.mBeanOperationInfo).collect(Collectors.toList())
|
||||
.toArray(new ModelMBeanOperationInfo[] {});
|
||||
Map<String, JmxOperation> operationsInfo = new LinkedHashMap<>();
|
||||
operationsMapping.forEach((name, t) -> operationsInfo.put(name, t.operation));
|
||||
MBeanInfo info = new ModelMBeanInfoSupport(EndpointMBean.class.getName(),
|
||||
getDescription(endpointInfo), new ModelMBeanAttributeInfo[0],
|
||||
new ModelMBeanConstructorInfo[0], operationsMBeanInfo,
|
||||
new ModelMBeanNotificationInfo[0]);
|
||||
return new EndpointMBeanInfo(endpointInfo.getId(), info, operationsInfo);
|
||||
}
|
||||
|
||||
private String getDescription(EndpointInfo<?> endpointInfo) {
|
||||
return "MBean operations for endpoint " + endpointInfo.getId();
|
||||
}
|
||||
|
||||
private Map<String, OperationInfos> getOperationInfo(
|
||||
EndpointInfo<JmxOperation> endpointInfo) {
|
||||
Map<String, OperationInfos> operationInfos = new HashMap<>();
|
||||
endpointInfo.getOperations().forEach((operationInfo) -> {
|
||||
String name = operationInfo.getOperationName();
|
||||
ModelMBeanOperationInfo mBeanOperationInfo = new ModelMBeanOperationInfo(
|
||||
operationInfo.getOperationName(), operationInfo.getDescription(),
|
||||
getMBeanParameterInfos(operationInfo), this.responseMapper
|
||||
.mapResponseType(operationInfo.getOutputType()).getName(),
|
||||
mapOperationType(operationInfo.getType()));
|
||||
operationInfos.put(name,
|
||||
new OperationInfos(mBeanOperationInfo, operationInfo));
|
||||
});
|
||||
return operationInfos;
|
||||
}
|
||||
|
||||
private MBeanParameterInfo[] getMBeanParameterInfos(JmxOperation operation) {
|
||||
return operation.getParameters().stream()
|
||||
.map((operationParameter) -> new MBeanParameterInfo(
|
||||
operationParameter.getName(),
|
||||
operationParameter.getType().getName(),
|
||||
operationParameter.getDescription()))
|
||||
.collect(Collectors.collectingAndThen(Collectors.toList(),
|
||||
(parameterInfos) -> parameterInfos
|
||||
.toArray(new MBeanParameterInfo[parameterInfos.size()])));
|
||||
}
|
||||
|
||||
private int mapOperationType(OperationType type) {
|
||||
if (type == OperationType.READ) {
|
||||
return MBeanOperationInfo.INFO;
|
||||
}
|
||||
if (type == OperationType.WRITE || type == OperationType.DELETE) {
|
||||
return MBeanOperationInfo.ACTION;
|
||||
}
|
||||
return MBeanOperationInfo.UNKNOWN;
|
||||
}
|
||||
|
||||
private static class OperationInfos {
|
||||
|
||||
private final ModelMBeanOperationInfo mBeanOperationInfo;
|
||||
|
||||
private final JmxOperation operation;
|
||||
|
||||
OperationInfos(ModelMBeanOperationInfo mBeanOperationInfo,
|
||||
JmxOperation operation) {
|
||||
this.mBeanOperationInfo = mBeanOperationInfo;
|
||||
this.operation = operation;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.jmx;
|
||||
|
||||
import javax.management.InstanceNotFoundException;
|
||||
import javax.management.MBeanRegistrationException;
|
||||
import javax.management.MBeanServer;
|
||||
import javax.management.MalformedObjectNameException;
|
||||
import javax.management.ObjectName;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.jmx.JmxException;
|
||||
import org.springframework.jmx.export.MBeanExportException;
|
||||
import org.springframework.jmx.export.MBeanExporter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* JMX Registrar for {@link EndpointMBean}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
* @see EndpointObjectNameFactory
|
||||
*/
|
||||
public class EndpointMBeanRegistrar {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(EndpointMBeanRegistrar.class);
|
||||
|
||||
private final MBeanServer mBeanServer;
|
||||
|
||||
private final EndpointObjectNameFactory objectNameFactory;
|
||||
|
||||
/**
|
||||
* Create a new instance with the {@link MBeanExporter} and
|
||||
* {@link EndpointObjectNameFactory} to use.
|
||||
* @param mBeanServer the mbean exporter
|
||||
* @param objectNameFactory the {@link ObjectName} factory
|
||||
*/
|
||||
public EndpointMBeanRegistrar(MBeanServer mBeanServer,
|
||||
EndpointObjectNameFactory objectNameFactory) {
|
||||
Assert.notNull(mBeanServer, "MBeanServer must not be null");
|
||||
Assert.notNull(objectNameFactory, "ObjectNameFactory must not be null");
|
||||
this.mBeanServer = mBeanServer;
|
||||
this.objectNameFactory = objectNameFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the specified {@link EndpointMBean} and return its {@link ObjectName}.
|
||||
* @param endpoint the endpoint to register
|
||||
* @return the {@link ObjectName} used to register the {@code endpoint}
|
||||
*/
|
||||
public ObjectName registerEndpointMBean(EndpointMBean endpoint) {
|
||||
Assert.notNull(endpoint, "Endpoint must not be null");
|
||||
try {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Registering endpoint with id '" + endpoint.getEndpointId()
|
||||
+ "' to the JMX domain");
|
||||
}
|
||||
ObjectName objectName = this.objectNameFactory.generate(endpoint);
|
||||
this.mBeanServer.registerMBean(endpoint, objectName);
|
||||
return objectName;
|
||||
}
|
||||
catch (MalformedObjectNameException ex) {
|
||||
throw new IllegalStateException("Invalid ObjectName for endpoint with id '"
|
||||
+ endpoint.getEndpointId() + "'", ex);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new MBeanExportException(
|
||||
"Failed to register MBean for endpoint with id '"
|
||||
+ endpoint.getEndpointId() + "'",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister the specified {@link ObjectName} if necessary.
|
||||
* @param objectName the {@link ObjectName} of the endpoint to unregister
|
||||
* @return {@code true} if the endpoint was unregistered, {@code false} if no such
|
||||
* endpoint was found
|
||||
*/
|
||||
public boolean unregisterEndpointMbean(ObjectName objectName) {
|
||||
try {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Unregister endpoint with ObjectName '" + objectName + "' "
|
||||
+ "from the JMX domain");
|
||||
}
|
||||
this.mBeanServer.unregisterMBean(objectName);
|
||||
return true;
|
||||
}
|
||||
catch (InstanceNotFoundException ex) {
|
||||
return false;
|
||||
}
|
||||
catch (MBeanRegistrationException ex) {
|
||||
throw new JmxException(
|
||||
"Failed to unregister MBean with ObjectName '" + objectName + "'",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -29,11 +29,13 @@ import javax.management.ObjectName;
|
||||
public interface EndpointObjectNameFactory {
|
||||
|
||||
/**
|
||||
* Generate an {@link ObjectName} for the specified {@link EndpointMBean endpoint}.
|
||||
* @param mBean the endpoint to handle
|
||||
* Generate an {@link ObjectName} for the specified {@link ExposableJmxEndpoint
|
||||
* endpoint}.
|
||||
* @param endpoint the endpoint MBean to handle
|
||||
* @return the {@link ObjectName} to use for the endpoint
|
||||
* @throws MalformedObjectNameException if the object name is invalid
|
||||
*/
|
||||
ObjectName generate(EndpointMBean mBean) throws MalformedObjectNameException;
|
||||
ObjectName getObjectName(ExposableJmxEndpoint endpoint)
|
||||
throws MalformedObjectNameException;
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.jmx;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
|
||||
|
||||
/**
|
||||
* Information describing an endpoint that can be exposed over JMX.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public interface ExposableJmxEndpoint extends ExposableEndpoint<JmxOperation> {
|
||||
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.jmx;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* {@link JmxOperationResponseMapper} that delegates to a Jackson {@link ObjectMapper} to
|
||||
* return a JSON response.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class JacksonJmxOperationResponseMapper implements JmxOperationResponseMapper {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private final JavaType listType;
|
||||
|
||||
private final JavaType mapType;
|
||||
|
||||
public JacksonJmxOperationResponseMapper(ObjectMapper objectMapper) {
|
||||
this.objectMapper = (objectMapper == null ? new ObjectMapper() : objectMapper);
|
||||
this.listType = this.objectMapper.getTypeFactory()
|
||||
.constructParametricType(List.class, Object.class);
|
||||
this.mapType = this.objectMapper.getTypeFactory()
|
||||
.constructParametricType(Map.class, String.class, Object.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> mapResponseType(Class<?> responseType) {
|
||||
if (CharSequence.class.isAssignableFrom(responseType)) {
|
||||
return String.class;
|
||||
}
|
||||
if (responseType.isArray() || Collection.class.isAssignableFrom(responseType)) {
|
||||
return List.class;
|
||||
}
|
||||
return Map.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object mapResponse(Object response) {
|
||||
if (response == null) {
|
||||
return null;
|
||||
}
|
||||
if (response instanceof CharSequence) {
|
||||
return response.toString();
|
||||
}
|
||||
if (response.getClass().isArray() || response instanceof Collection) {
|
||||
return this.objectMapper.convertValue(response, this.listType);
|
||||
}
|
||||
return this.objectMapper.convertValue(response, this.mapType);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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.jmx;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.management.InstanceNotFoundException;
|
||||
import javax.management.MBeanRegistrationException;
|
||||
import javax.management.MBeanServer;
|
||||
import javax.management.MalformedObjectNameException;
|
||||
import javax.management.ObjectName;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.jmx.JmxException;
|
||||
import org.springframework.jmx.export.MBeanExportException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Exports {@link ExposableJmxEndpoint JMX endpoints} to a {@link MBeanServer}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class JmxEndpointExporter implements InitializingBean, DisposableBean {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(JmxEndpointExporter.class);
|
||||
|
||||
private final MBeanServer mBeanServer;
|
||||
|
||||
private final EndpointObjectNameFactory objectNameFactory;
|
||||
|
||||
private final JmxOperationResponseMapper responseMapper;
|
||||
|
||||
private final Collection<ExposableJmxEndpoint> endpoints;
|
||||
|
||||
private Collection<ObjectName> registered;
|
||||
|
||||
public JmxEndpointExporter(MBeanServer mBeanServer,
|
||||
EndpointObjectNameFactory objectNameFactory,
|
||||
JmxOperationResponseMapper responseMapper,
|
||||
Collection<? extends ExposableJmxEndpoint> endpoints) {
|
||||
Assert.notNull(mBeanServer, "MBeanServer must not be null");
|
||||
Assert.notNull(objectNameFactory, "ObjectNameFactory must not be null");
|
||||
Assert.notNull(responseMapper, "ResponseMapper must not be null");
|
||||
Assert.notNull(endpoints, "Endpoints must not be null");
|
||||
this.mBeanServer = mBeanServer;
|
||||
this.objectNameFactory = objectNameFactory;
|
||||
this.responseMapper = responseMapper;
|
||||
this.endpoints = Collections.unmodifiableCollection(endpoints);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
this.registered = register();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
unregister(this.registered);
|
||||
}
|
||||
|
||||
private Collection<ObjectName> register() {
|
||||
return this.endpoints.stream().map(this::register).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private ObjectName register(ExposableJmxEndpoint endpoint) {
|
||||
Assert.notNull(endpoint, "Endpoint must not be null");
|
||||
try {
|
||||
ObjectName name = this.objectNameFactory.getObjectName(endpoint);
|
||||
EndpointMBean mbean = new EndpointMBean(this.responseMapper, endpoint);
|
||||
this.mBeanServer.registerMBean(mbean, name);
|
||||
return name;
|
||||
}
|
||||
catch (MalformedObjectNameException ex) {
|
||||
throw new IllegalStateException(
|
||||
"Invalid ObjectName for " + getEndpointDescription(endpoint), ex);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new MBeanExportException(
|
||||
"Failed to register MBean for " + getEndpointDescription(endpoint),
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void unregister(Collection<ObjectName> objectNames) {
|
||||
objectNames.forEach(this::unregister);
|
||||
}
|
||||
|
||||
private void unregister(ObjectName objectName) {
|
||||
try {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Unregister endpoint with ObjectName '" + objectName + "' "
|
||||
+ "from the JMX domain");
|
||||
}
|
||||
this.mBeanServer.unregisterMBean(objectName);
|
||||
}
|
||||
catch (InstanceNotFoundException ex) {
|
||||
// Ignore and continue
|
||||
}
|
||||
catch (MBeanRegistrationException ex) {
|
||||
throw new JmxException(
|
||||
"Failed to unregister MBean with ObjectName '" + objectName + "'",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
private String getEndpointDescription(ExposableJmxEndpoint endpoint) {
|
||||
return "endpoint '" + endpoint.getId() + "'";
|
||||
}
|
||||
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.jmx;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
|
||||
/**
|
||||
* A factory for creating JMX MBeans for endpoint operations.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class JmxEndpointMBeanFactory {
|
||||
|
||||
private final EndpointMBeanInfoAssembler assembler;
|
||||
|
||||
private final JmxOperationResponseMapper resultMapper;
|
||||
|
||||
/**
|
||||
* Create a new {@link JmxEndpointMBeanFactory} instance that will use the given
|
||||
* {@code responseMapper} to convert an operation's response to a JMX-friendly form.
|
||||
* @param responseMapper the response mapper
|
||||
*/
|
||||
public JmxEndpointMBeanFactory(JmxOperationResponseMapper responseMapper) {
|
||||
this.assembler = new EndpointMBeanInfoAssembler(responseMapper);
|
||||
this.resultMapper = responseMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates MBeans for the given {@code endpoints}.
|
||||
* @param endpoints the endpoints
|
||||
* @return the MBeans
|
||||
*/
|
||||
public Collection<EndpointMBean> createMBeans(
|
||||
Collection<EndpointInfo<JmxOperation>> endpoints) {
|
||||
return endpoints.stream().map(this::createMBean).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private EndpointMBean createMBean(EndpointInfo<JmxOperation> endpointInfo) {
|
||||
EndpointMBeanInfo endpointMBeanInfo = this.assembler
|
||||
.createEndpointMBeanInfo(endpointInfo);
|
||||
return new EndpointMBean(this.resultMapper::mapResponse, endpointMBeanInfo);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.jmx;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointsSupplier;
|
||||
|
||||
/**
|
||||
* {@link EndpointsSupplier} for {@link ExposableJmxEndpoint JMX endpoints}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public interface JmxEndpointsSupplier extends EndpointsSupplier<ExposableJmxEndpoint> {
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -16,90 +16,43 @@
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.jmx;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.Operation;
|
||||
import org.springframework.boot.actuate.endpoint.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
|
||||
/**
|
||||
* An operation on a JMX endpoint.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class JmxOperation extends Operation {
|
||||
|
||||
private final String operationName;
|
||||
|
||||
private final Class<?> outputType;
|
||||
|
||||
private final String description;
|
||||
|
||||
private final List<JmxEndpointOperationParameterInfo> parameters;
|
||||
|
||||
/**
|
||||
* Creates a new {@code JmxEndpointOperation} for an operation of the given
|
||||
* {@code type}. The operation can be performed using the given {@code invoker}.
|
||||
* @param type the type of the operation
|
||||
* @param invoker used to perform the operation
|
||||
* @param operationName the name of the operation
|
||||
* @param outputType the type of the output of the operation
|
||||
* @param description the description of the operation
|
||||
* @param parameters the parameters of the operation
|
||||
*/
|
||||
public JmxOperation(OperationType type, OperationInvoker invoker,
|
||||
String operationName, Class<?> outputType, String description,
|
||||
List<JmxEndpointOperationParameterInfo> parameters) {
|
||||
super(type, invoker, true);
|
||||
this.operationName = operationName;
|
||||
this.outputType = outputType;
|
||||
this.description = description;
|
||||
this.parameters = parameters;
|
||||
}
|
||||
public interface JmxOperation extends Operation {
|
||||
|
||||
/**
|
||||
* Returns the name of the operation.
|
||||
* @return the operation name
|
||||
*/
|
||||
public String getOperationName() {
|
||||
return this.operationName;
|
||||
}
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Returns the type of the output of the operation.
|
||||
* @return the output type
|
||||
*/
|
||||
public Class<?> getOutputType() {
|
||||
return this.outputType;
|
||||
}
|
||||
Class<?> getOutputType();
|
||||
|
||||
/**
|
||||
* Returns the description of the operation.
|
||||
* @return the operation description
|
||||
*/
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
String getDescription();
|
||||
|
||||
/**
|
||||
* Returns the parameters of the operation.
|
||||
* @return the operation parameters
|
||||
* Returns the parameters the operation expects in the order that they should be
|
||||
* provided.
|
||||
* @return the operation parameter names
|
||||
*/
|
||||
public List<JmxEndpointOperationParameterInfo> getParameters() {
|
||||
return Collections.unmodifiableList(this.parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("type", getType())
|
||||
.append("invoker", getInvoker()).append("blocking", isBlocking())
|
||||
.append("operationName", this.operationName)
|
||||
.append("outputType", this.outputType)
|
||||
.append("description", this.description).toString();
|
||||
}
|
||||
List<JmxOperationParameter> getParameters();
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -20,37 +20,27 @@ package org.springframework.boot.actuate.endpoint.jmx;
|
||||
* Describes the parameters of an operation on a JMX endpoint.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class JmxEndpointOperationParameterInfo {
|
||||
public interface JmxOperationParameter {
|
||||
|
||||
private final String name;
|
||||
/**
|
||||
* Return the name of the operation parameter.
|
||||
* @return the name of the parameter
|
||||
*/
|
||||
String getName();
|
||||
|
||||
private final Class<?> type;
|
||||
|
||||
private final String description;
|
||||
|
||||
public JmxEndpointOperationParameterInfo(String name, Class<?> type,
|
||||
String description) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public Class<?> getType() {
|
||||
return this.type;
|
||||
}
|
||||
/**
|
||||
* Return the type of the operation parameter.
|
||||
* @return the type
|
||||
*/
|
||||
Class<?> getType();
|
||||
|
||||
/**
|
||||
* Return the description of the parameter or {@code null} if none is available.
|
||||
* @return the description or {@code null}
|
||||
*/
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
String getDescription();
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -17,21 +17,13 @@
|
||||
package org.springframework.boot.actuate.endpoint.jmx;
|
||||
|
||||
/**
|
||||
* A {@code JmxOperationResponseMapper} maps an operation's response to a JMX-friendly
|
||||
* form.
|
||||
* Maps an operation's response to a JMX-friendly form.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public interface JmxOperationResponseMapper {
|
||||
|
||||
/**
|
||||
* Map the operation's response so that it can be consumed by a JMX compliant client.
|
||||
* @param response the operation's response
|
||||
* @return the {@code response}, in a JMX compliant format
|
||||
*/
|
||||
Object mapResponse(Object response);
|
||||
|
||||
/**
|
||||
* Map the response type to its JMX compliant counterpart.
|
||||
* @param responseType the operation's response type
|
||||
@ -39,4 +31,11 @@ public interface JmxOperationResponseMapper {
|
||||
*/
|
||||
Class<?> mapResponseType(Class<?> responseType);
|
||||
|
||||
/**
|
||||
* Map the operation's response so that it can be consumed by a JMX compliant client.
|
||||
* @param response the operation's response
|
||||
* @return the {@code response}, in a JMX compliant format
|
||||
*/
|
||||
Object mapResponse(Object response);
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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.jmx;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.management.MBeanInfo;
|
||||
import javax.management.MBeanOperationInfo;
|
||||
import javax.management.MBeanParameterInfo;
|
||||
import javax.management.modelmbean.ModelMBeanAttributeInfo;
|
||||
import javax.management.modelmbean.ModelMBeanConstructorInfo;
|
||||
import javax.management.modelmbean.ModelMBeanInfoSupport;
|
||||
import javax.management.modelmbean.ModelMBeanNotificationInfo;
|
||||
import javax.management.modelmbean.ModelMBeanOperationInfo;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||
|
||||
/**
|
||||
* Factory to create {@link MBeanInfo} from a {@link ExposableJmxEndpoint}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class MBeanInfoFactory {
|
||||
|
||||
private static final ModelMBeanAttributeInfo[] NO_ATTRIBUTES = new ModelMBeanAttributeInfo[0];
|
||||
|
||||
private static final ModelMBeanConstructorInfo[] NO_CONSTRUCTORS = new ModelMBeanConstructorInfo[0];
|
||||
|
||||
private static final ModelMBeanNotificationInfo[] NO_NOTIFICATIONS = new ModelMBeanNotificationInfo[0];
|
||||
|
||||
private final JmxOperationResponseMapper responseMapper;
|
||||
|
||||
MBeanInfoFactory(JmxOperationResponseMapper responseMapper) {
|
||||
this.responseMapper = responseMapper;
|
||||
}
|
||||
|
||||
public MBeanInfo getMBeanInfo(ExposableJmxEndpoint endpoint) {
|
||||
String className = EndpointMBean.class.getName();
|
||||
String description = getDescription(endpoint);
|
||||
ModelMBeanOperationInfo[] operations = getMBeanOperations(endpoint);
|
||||
return new ModelMBeanInfoSupport(className, description, NO_ATTRIBUTES,
|
||||
NO_CONSTRUCTORS, operations, NO_NOTIFICATIONS);
|
||||
}
|
||||
|
||||
private String getDescription(ExposableJmxEndpoint endpoint) {
|
||||
return "MBean operations for endpoint " + endpoint.getId();
|
||||
}
|
||||
|
||||
private ModelMBeanOperationInfo[] getMBeanOperations(ExposableJmxEndpoint endpoint) {
|
||||
return endpoint.getOperations().stream().map(this::getMBeanOperation)
|
||||
.toArray(ModelMBeanOperationInfo[]::new);
|
||||
}
|
||||
|
||||
private ModelMBeanOperationInfo getMBeanOperation(JmxOperation operation) {
|
||||
String name = operation.getName();
|
||||
String description = operation.getDescription();
|
||||
MBeanParameterInfo[] signature = getSignature(operation.getParameters());
|
||||
String type = getType(operation.getOutputType());
|
||||
int impact = getImact(operation.getType());
|
||||
return new ModelMBeanOperationInfo(name, description, signature, type, impact);
|
||||
}
|
||||
|
||||
private MBeanParameterInfo[] getSignature(List<JmxOperationParameter> parameters) {
|
||||
return parameters.stream().map(this::getMBeanParameter)
|
||||
.toArray(MBeanParameterInfo[]::new);
|
||||
}
|
||||
|
||||
private MBeanParameterInfo getMBeanParameter(JmxOperationParameter parameter) {
|
||||
return new MBeanParameterInfo(parameter.getName(), parameter.getType().getName(),
|
||||
parameter.getDescription());
|
||||
}
|
||||
|
||||
private int getImact(OperationType operationType) {
|
||||
if (operationType == OperationType.READ) {
|
||||
return MBeanOperationInfo.INFO;
|
||||
}
|
||||
if (operationType == OperationType.WRITE
|
||||
|| operationType == OperationType.DELETE) {
|
||||
return MBeanOperationInfo.ACTION;
|
||||
}
|
||||
return MBeanOperationInfo.UNKNOWN;
|
||||
}
|
||||
|
||||
private String getType(Class<?> outputType) {
|
||||
return this.responseMapper.mapResponseType(outputType).getName();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.jmx.annotation;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.annotation.AbstractDiscoveredEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.ExposableJmxEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.JmxOperation;
|
||||
|
||||
/**
|
||||
* A discovered {@link ExposableJmxEndpoint JMX endpoint}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class DiscoveredJmxEndpoint extends AbstractDiscoveredEndpoint<JmxOperation>
|
||||
implements ExposableJmxEndpoint {
|
||||
|
||||
DiscoveredJmxEndpoint(EndpointDiscoverer<?, ?> discoverer, String id,
|
||||
boolean enabledByDefault, Collection<JmxOperation> operations) {
|
||||
super(discoverer, id, enabledByDefault, operations);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,213 @@
|
||||
/*
|
||||
* 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.jmx.annotation;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.annotation.AbstractDiscoveredOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationParameter;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationParameters;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.reflect.OperationMethod;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.JmxOperation;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.JmxOperationParameter;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource;
|
||||
import org.springframework.jmx.export.metadata.JmxAttributeSource;
|
||||
import org.springframework.jmx.export.metadata.ManagedOperation;
|
||||
import org.springframework.jmx.export.metadata.ManagedOperationParameter;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A discovered {@link JmxOperation JMX operation}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Philip Webb
|
||||
*/
|
||||
class DiscoveredJmxOperation extends AbstractDiscoveredOperation implements JmxOperation {
|
||||
|
||||
private static final JmxAttributeSource jmxAttributeSource = new AnnotationJmxAttributeSource();
|
||||
|
||||
private final String name;
|
||||
|
||||
private final Class<?> outputType;
|
||||
|
||||
private final String description;
|
||||
|
||||
private final List<JmxOperationParameter> parameters;
|
||||
|
||||
DiscoveredJmxOperation(String endpointId, DiscoveredOperationMethod operationMethod,
|
||||
OperationInvoker invoker) {
|
||||
super(operationMethod, invoker);
|
||||
Method method = operationMethod.getMethod();
|
||||
this.name = method.getName();
|
||||
this.outputType = JmxType.get(method.getReturnType());
|
||||
this.description = getDescription(method,
|
||||
() -> "Invoke " + this.name + " for endpoint " + endpointId);
|
||||
this.parameters = getParameters(operationMethod);
|
||||
}
|
||||
|
||||
private String getDescription(Method method, Supplier<String> fallback) {
|
||||
ManagedOperation managed = jmxAttributeSource.getManagedOperation(method);
|
||||
if (managed != null && StringUtils.hasText(managed.getDescription())) {
|
||||
return managed.getDescription();
|
||||
}
|
||||
return fallback.get();
|
||||
}
|
||||
|
||||
private List<JmxOperationParameter> getParameters(OperationMethod operationMethod) {
|
||||
if (!operationMethod.getParameters().hasParameters()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Method method = operationMethod.getMethod();
|
||||
ManagedOperationParameter[] managed = jmxAttributeSource
|
||||
.getManagedOperationParameters(method);
|
||||
if (managed.length == 0) {
|
||||
return asList(operationMethod.getParameters().stream()
|
||||
.map(DiscoveredJmxOperationParameter::new));
|
||||
}
|
||||
return mergeParameters(operationMethod.getParameters(), managed);
|
||||
}
|
||||
|
||||
private List<JmxOperationParameter> mergeParameters(
|
||||
OperationParameters operationParameters,
|
||||
ManagedOperationParameter[] managedParameters) {
|
||||
List<JmxOperationParameter> merged = new ArrayList<>(managedParameters.length);
|
||||
for (int i = 0; i < managedParameters.length; i++) {
|
||||
merged.add(new DiscoveredJmxOperationParameter(managedParameters[i],
|
||||
operationParameters.get(i)));
|
||||
}
|
||||
return Collections.unmodifiableList(merged);
|
||||
}
|
||||
|
||||
private <T> List<T> asList(Stream<T> stream) {
|
||||
return stream.collect(Collectors.collectingAndThen(Collectors.toList(),
|
||||
Collections::unmodifiableList));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getOutputType() {
|
||||
return this.outputType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JmxOperationParameter> getParameters() {
|
||||
return this.parameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void appendFields(ToStringCreator creator) {
|
||||
creator.append("name", this.name).append("outputType", this.outputType)
|
||||
.append("description", this.description)
|
||||
.append("parameters", this.parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* A discovered {@link JmxOperationParameter}.
|
||||
*/
|
||||
private static class DiscoveredJmxOperationParameter
|
||||
implements JmxOperationParameter {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final Class<?> type;
|
||||
|
||||
private final String description;
|
||||
|
||||
DiscoveredJmxOperationParameter(OperationParameter operationParameter) {
|
||||
this.name = operationParameter.getName();
|
||||
this.type = JmxType.get(operationParameter.getType());
|
||||
this.description = null;
|
||||
}
|
||||
|
||||
DiscoveredJmxOperationParameter(ManagedOperationParameter managedParameter,
|
||||
OperationParameter operationParameter) {
|
||||
this.name = managedParameter.getName();
|
||||
this.type = JmxType.get(operationParameter.getType());
|
||||
this.description = managedParameter.getDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder(this.name);
|
||||
if (this.description != null) {
|
||||
result.append(" (" + this.description + ")");
|
||||
}
|
||||
result.append(":" + this.type);
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to convert to JMX supported types.
|
||||
*/
|
||||
private static class JmxType {
|
||||
|
||||
public static Class<?> get(Class<?> source) {
|
||||
if (source.isEnum()) {
|
||||
return String.class;
|
||||
}
|
||||
if (Date.class.isAssignableFrom(source)
|
||||
|| Instant.class.isAssignableFrom(source)) {
|
||||
return String.class;
|
||||
}
|
||||
if (source.getName().startsWith("java.")) {
|
||||
return source;
|
||||
}
|
||||
if (source.equals(Void.TYPE)) {
|
||||
return source;
|
||||
}
|
||||
return Object.class;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.jmx.annotation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointFilter;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.AnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.JmxOperation;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInvokerAdvisor;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource;
|
||||
|
||||
/**
|
||||
* Discovers the {@link Endpoint endpoints} in an {@link ApplicationContext} with
|
||||
* {@link EndpointJmxExtension JMX extensions} applied to them.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class JmxAnnotationEndpointDiscoverer
|
||||
extends AnnotationEndpointDiscoverer<String, JmxOperation> {
|
||||
|
||||
static final AnnotationJmxAttributeSource jmxAttributeSource = new AnnotationJmxAttributeSource();
|
||||
|
||||
/**
|
||||
* Creates a new {@link JmxAnnotationEndpointDiscoverer} that will discover
|
||||
* {@link Endpoint endpoints} and {@link EndpointJmxExtension jmx extensions} using
|
||||
* the given {@link ApplicationContext}.
|
||||
* @param applicationContext the application context
|
||||
* @param parameterMapper the {@link ParameterMapper} used to convert arguments when
|
||||
* an operation is invoked
|
||||
* @param invokerAdvisors advisors used to add additional invoker advise
|
||||
* @param filters filters that must match for an endpoint to be exposed
|
||||
*/
|
||||
public JmxAnnotationEndpointDiscoverer(ApplicationContext applicationContext,
|
||||
ParameterMapper parameterMapper,
|
||||
Collection<? extends OperationMethodInvokerAdvisor> invokerAdvisors,
|
||||
Collection<? extends EndpointFilter<JmxOperation>> filters) {
|
||||
super(applicationContext, new JmxEndpointOperationFactory(),
|
||||
JmxOperation::getOperationName, parameterMapper, invokerAdvisors,
|
||||
filters);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void verify(Collection<DiscoveredEndpoint> exposedEndpoints) {
|
||||
List<List<JmxOperation>> clashes = new ArrayList<>();
|
||||
exposedEndpoints.forEach((exposedEndpoint) -> clashes
|
||||
.addAll(exposedEndpoint.findDuplicateOperations().values()));
|
||||
if (!clashes.isEmpty()) {
|
||||
StringBuilder message = new StringBuilder();
|
||||
message.append(
|
||||
String.format("Found multiple JMX operations with the same name:%n"));
|
||||
clashes.forEach((clash) -> {
|
||||
message.append(" ").append(clash.get(0).getOperationName())
|
||||
.append(String.format(":%n"));
|
||||
clash.forEach((operation) -> message.append(" ")
|
||||
.append(String.format("%s%n", operation)));
|
||||
});
|
||||
throw new IllegalStateException(message.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -22,7 +22,6 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.annotation.AnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.FilteredEndpoint;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
@ -33,7 +32,6 @@ import org.springframework.core.annotation.AliasFor;
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
* @see AnnotationEndpointDiscoverer
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.jmx.annotation;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointFilter;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.ExposableJmxEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.JmxEndpointsSupplier;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.JmxOperation;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* {@link EndpointDiscoverer} for {@link ExposableJmxEndpoint JMX endpoints}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class JmxEndpointDiscoverer
|
||||
extends EndpointDiscoverer<ExposableJmxEndpoint, JmxOperation>
|
||||
implements JmxEndpointsSupplier {
|
||||
|
||||
/**
|
||||
* Create a new {@link JmxEndpointDiscoverer} instance.
|
||||
* @param applicationContext the source application context
|
||||
* @param parameterValueMapper the parameter value mapper
|
||||
* @param invokerAdvisors invoker advisors to apply
|
||||
* @param filters filters to apply
|
||||
*/
|
||||
public JmxEndpointDiscoverer(ApplicationContext applicationContext,
|
||||
ParameterValueMapper parameterValueMapper,
|
||||
Collection<OperationInvokerAdvisor> invokerAdvisors,
|
||||
Collection<EndpointFilter<ExposableJmxEndpoint>> filters) {
|
||||
super(applicationContext, parameterValueMapper, invokerAdvisors, filters);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExposableJmxEndpoint createEndpoint(String id, boolean enabledByDefault,
|
||||
Collection<JmxOperation> operations) {
|
||||
return new DiscoveredJmxEndpoint(this, id, enabledByDefault, operations);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JmxOperation createOperation(String endpointId,
|
||||
DiscoveredOperationMethod operationMethod, OperationInvoker invoker) {
|
||||
return new DiscoveredJmxOperation(endpointId, operationMethod, invoker);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OperationKey createOperationKey(JmxOperation operation) {
|
||||
return new OperationKey(operation.getName(),
|
||||
() -> "MBean call '" + operation.getName() + "'");
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -16,23 +16,18 @@
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.jmx.annotation;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointFilter;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.JmxOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.DiscovererEndpointFilter;
|
||||
|
||||
/**
|
||||
* {@link EndpointFilter} for endpoints discovered by
|
||||
* {@link JmxAnnotationEndpointDiscoverer}.
|
||||
* {@link EndpointFilter} for endpoints discovered by {@link JmxEndpointDiscoverer}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class JmxEndpointFilter implements EndpointFilter<JmxOperation> {
|
||||
class JmxEndpointFilter extends DiscovererEndpointFilter {
|
||||
|
||||
@Override
|
||||
public boolean match(EndpointInfo<JmxOperation> info,
|
||||
EndpointDiscoverer<JmxOperation> discoverer) {
|
||||
return (discoverer instanceof JmxAnnotationEndpointDiscoverer);
|
||||
JmxEndpointFilter() {
|
||||
super(JmxEndpointDiscoverer.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,128 +0,0 @@
|
||||
/*
|
||||
* 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.jmx.annotation;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.OperationFactory;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.JmxEndpointOperationParameterInfo;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.JmxOperation;
|
||||
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInfo;
|
||||
import org.springframework.jmx.export.metadata.ManagedOperation;
|
||||
import org.springframework.jmx.export.metadata.ManagedOperationParameter;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link OperationFactory} for {@link JmxOperation JMX operations}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class JmxEndpointOperationFactory implements OperationFactory<JmxOperation> {
|
||||
|
||||
@Override
|
||||
public JmxOperation createOperation(String endpointId, OperationMethodInfo methodInfo,
|
||||
Object target, OperationInvoker invoker) {
|
||||
Method method = methodInfo.getMethod();
|
||||
String name = method.getName();
|
||||
OperationType operationType = methodInfo.getOperationType();
|
||||
Class<?> outputType = getJmxType(method.getReturnType());
|
||||
String description = getDescription(method,
|
||||
() -> "Invoke " + name + " for endpoint " + endpointId);
|
||||
return new JmxOperation(operationType, invoker, name, outputType, description,
|
||||
getParameters(methodInfo));
|
||||
}
|
||||
|
||||
private String getDescription(Method method, Supplier<String> fallback) {
|
||||
ManagedOperation managedOperation = JmxAnnotationEndpointDiscoverer.jmxAttributeSource
|
||||
.getManagedOperation(method);
|
||||
if (managedOperation != null
|
||||
&& StringUtils.hasText(managedOperation.getDescription())) {
|
||||
return managedOperation.getDescription();
|
||||
}
|
||||
return fallback.get();
|
||||
}
|
||||
|
||||
private List<JmxEndpointOperationParameterInfo> getParameters(
|
||||
OperationMethodInfo methodInfo) {
|
||||
if (methodInfo.getParameters().isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Method method = methodInfo.getMethod();
|
||||
ManagedOperationParameter[] operationParameters = JmxAnnotationEndpointDiscoverer.jmxAttributeSource
|
||||
.getManagedOperationParameters(method);
|
||||
if (operationParameters.length == 0) {
|
||||
return methodInfo.getParameters().entrySet().stream().map(this::getParameter)
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
}
|
||||
return mergeParameters(method.getParameters(), operationParameters);
|
||||
}
|
||||
|
||||
private List<JmxEndpointOperationParameterInfo> mergeParameters(
|
||||
Parameter[] methodParameters,
|
||||
ManagedOperationParameter[] operationParameters) {
|
||||
List<JmxEndpointOperationParameterInfo> parameters = new ArrayList<>();
|
||||
for (int i = 0; i < operationParameters.length; i++) {
|
||||
ManagedOperationParameter operationParameter = operationParameters[i];
|
||||
Parameter methodParameter = methodParameters[i];
|
||||
JmxEndpointOperationParameterInfo parameter = getParameter(
|
||||
operationParameter.getName(), methodParameter,
|
||||
operationParameter.getDescription());
|
||||
parameters.add(parameter);
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private JmxEndpointOperationParameterInfo getParameter(
|
||||
Map.Entry<String, Parameter> entry) {
|
||||
return getParameter(entry.getKey(), entry.getValue(), null);
|
||||
}
|
||||
|
||||
private JmxEndpointOperationParameterInfo getParameter(String name,
|
||||
Parameter methodParameter, String description) {
|
||||
return new JmxEndpointOperationParameterInfo(name,
|
||||
getJmxType(methodParameter.getType()), description);
|
||||
}
|
||||
|
||||
private Class<?> getJmxType(Class<?> type) {
|
||||
if (type.isEnum()) {
|
||||
return String.class;
|
||||
}
|
||||
if (Instant.class.isAssignableFrom(type)) {
|
||||
return String.class;
|
||||
}
|
||||
if (type.getName().startsWith("java.")) {
|
||||
return type;
|
||||
}
|
||||
if (type.equals(Void.TYPE)) {
|
||||
return type;
|
||||
}
|
||||
return Object.class;
|
||||
}
|
||||
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.reflect;
|
||||
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.OperationInvoker;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* An {@code OperationInvoker} that invokes an operation using reflection.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class ReflectiveOperationInvoker implements OperationInvoker {
|
||||
|
||||
private final Object target;
|
||||
|
||||
private final OperationMethodInfo methodInfo;
|
||||
|
||||
private final ParameterMapper parameterMapper;
|
||||
|
||||
/**
|
||||
* Creates a new {code ReflectiveOperationInvoker} that will invoke the given
|
||||
* {@code method} on the given {@code target}. The given {@code parameterMapper} will
|
||||
* be used to map parameters to the required types and the given
|
||||
* {@code parameterNameMapper} will be used map parameters by name.
|
||||
* @param target the target of the reflective call
|
||||
* @param methodInfo the method info
|
||||
* @param parameterMapper the parameter mapper
|
||||
*/
|
||||
public ReflectiveOperationInvoker(Object target, OperationMethodInfo methodInfo,
|
||||
ParameterMapper parameterMapper) {
|
||||
Assert.notNull(target, "Target must not be null");
|
||||
Assert.notNull(methodInfo, "MethodInfo must not be null");
|
||||
Assert.notNull(parameterMapper, "ParameterMapper must not be null");
|
||||
ReflectionUtils.makeAccessible(methodInfo.getMethod());
|
||||
this.target = target;
|
||||
this.methodInfo = methodInfo;
|
||||
this.parameterMapper = parameterMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Map<String, Object> arguments) {
|
||||
Map<String, Parameter> parameters = this.methodInfo.getParameters();
|
||||
validateRequiredParameters(parameters, arguments);
|
||||
return ReflectionUtils.invokeMethod(this.methodInfo.getMethod(), this.target,
|
||||
resolveArguments(parameters, arguments));
|
||||
}
|
||||
|
||||
private void validateRequiredParameters(Map<String, Parameter> parameters,
|
||||
Map<String, Object> arguments) {
|
||||
Set<String> missingParameters = parameters.keySet().stream()
|
||||
.filter((n) -> isMissing(n, parameters.get(n), arguments))
|
||||
.collect(Collectors.toSet());
|
||||
if (!missingParameters.isEmpty()) {
|
||||
throw new ParametersMissingException(missingParameters);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMissing(String name, Parameter parameter,
|
||||
Map<String, Object> arguments) {
|
||||
Object resolved = arguments.get(name);
|
||||
return (resolved == null && !isExplicitNullable(parameter));
|
||||
}
|
||||
|
||||
private boolean isExplicitNullable(Parameter parameter) {
|
||||
return (parameter.getAnnotationsByType(Nullable.class).length != 0);
|
||||
}
|
||||
|
||||
private Object[] resolveArguments(Map<String, Parameter> parameters,
|
||||
Map<String, Object> arguments) {
|
||||
return parameters.keySet().stream()
|
||||
.map((name) -> resolveArgument(name, parameters.get(name), arguments))
|
||||
.collect(Collectors.collectingAndThen(Collectors.toList(),
|
||||
(list) -> list.toArray(new Object[list.size()])));
|
||||
}
|
||||
|
||||
private Object resolveArgument(String name, Parameter parameter,
|
||||
Map<String, Object> arguments) {
|
||||
Object resolved = arguments.get(name);
|
||||
return this.parameterMapper.mapParameter(resolved, parameter.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("target", this.target)
|
||||
.append("method", this.methodInfo.getMethod().toString()).toString();
|
||||
}
|
||||
|
||||
}
|
@ -20,8 +20,6 @@ import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
|
||||
/**
|
||||
* A resolver for {@link Link links} to web endpoints.
|
||||
*
|
||||
@ -33,18 +31,18 @@ public class EndpointLinksResolver {
|
||||
/**
|
||||
* Resolves links to the operations of the given {code webEndpoints} based on a
|
||||
* request with the given {@code requestUrl}.
|
||||
* @param webEndpoints the web endpoints
|
||||
* @param endpoints the source endpoints
|
||||
* @param requestUrl the url of the request for the endpoint links
|
||||
* @return the links
|
||||
*/
|
||||
public Map<String, Link> resolveLinks(
|
||||
Collection<EndpointInfo<WebOperation>> webEndpoints, String requestUrl) {
|
||||
public Map<String, Link> resolveLinks(Collection<ExposableWebEndpoint> endpoints,
|
||||
String requestUrl) {
|
||||
String normalizedUrl = normalizeRequestUrl(requestUrl);
|
||||
Map<String, Link> links = new LinkedHashMap<>();
|
||||
links.put("self", new Link(normalizedUrl));
|
||||
for (EndpointInfo<WebOperation> endpoint : webEndpoints) {
|
||||
for (ExposableWebEndpoint endpoint : endpoints) {
|
||||
for (WebOperation operation : endpoint.getOperations()) {
|
||||
webEndpoints.stream().map(EndpointInfo::getId).forEach((id) -> links
|
||||
endpoints.stream().map(ExposableWebEndpoint::getId).forEach((id) -> links
|
||||
.put(operation.getId(), createLink(normalizedUrl, operation)));
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -49,7 +49,6 @@ public class EndpointMediaTypes {
|
||||
|
||||
/**
|
||||
* Returns the media types produced by an endpoint.
|
||||
*
|
||||
* @return the produced media types
|
||||
*/
|
||||
public List<String> getProduced() {
|
||||
@ -58,7 +57,6 @@ public class EndpointMediaTypes {
|
||||
|
||||
/**
|
||||
* Returns the media types consumed by an endpoint.
|
||||
*
|
||||
* @return the consumed media types
|
||||
*/
|
||||
public List<String> getConsumed() {
|
||||
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.web;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
|
||||
|
||||
/**
|
||||
* Information describing an endpoint that can be exposed over the web.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public interface ExposableWebEndpoint extends ExposableEndpoint<WebOperation> {
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* 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.
|
||||
@ -17,6 +17,7 @@
|
||||
package org.springframework.boot.actuate.endpoint.web;
|
||||
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Details for a link in a
|
||||
@ -37,6 +38,7 @@ public class Link {
|
||||
* @param href the href
|
||||
*/
|
||||
public Link(String href) {
|
||||
Assert.notNull(href, "HREF must not be null");
|
||||
this.href = href;
|
||||
this.templated = href.contains("{");
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user