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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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";
|
public static final String REQUEST_ATTRIBUTE = "cloudFoundryAccessLevel";
|
||||||
|
|
||||||
private final List<String> endpointPaths;
|
private final List<String> endpointIds;
|
||||||
|
|
||||||
AccessLevel(String... endpointPaths) {
|
AccessLevel(String... endpointIds) {
|
||||||
this.endpointPaths = Arrays.asList(endpointPaths);
|
this.endpointIds = Arrays.asList(endpointIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns if the access level should allow access to the specified endpoint path.
|
* 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
|
* @return {@code true} if access is allowed
|
||||||
*/
|
*/
|
||||||
public boolean isAccessAllowed(String endpointPath) {
|
public boolean isAccessAllowed(String endpointId) {
|
||||||
return this.endpointPaths.isEmpty() || this.endpointPaths.contains(endpointPath);
|
return this.endpointIds.isEmpty() || this.endpointIds.contains(endpointId);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,22 +16,19 @@
|
||||||
|
|
||||||
package org.springframework.boot.actuate.autoconfigure.cloudfoundry;
|
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.EndpointFilter;
|
||||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
import org.springframework.boot.actuate.endpoint.annotation.DiscovererEndpointFilter;
|
||||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link EndpointFilter} for endpoints discovered by
|
* {@link EndpointFilter} for endpoints discovered by
|
||||||
* {@link CloudFoundryWebAnnotationEndpointDiscoverer}.
|
* {@link CloudFoundryWebEndpointDiscoverer}.
|
||||||
*
|
*
|
||||||
* @author Madhura Bhave
|
* @author Madhura Bhave
|
||||||
*/
|
*/
|
||||||
public class CloudFoundryEndpointFilter implements EndpointFilter<WebOperation> {
|
class CloudFoundryEndpointFilter extends DiscovererEndpointFilter {
|
||||||
|
|
||||||
@Override
|
protected CloudFoundryEndpointFilter() {
|
||||||
public boolean match(EndpointInfo<WebOperation> info, EndpointDiscoverer<WebOperation> discoverer) {
|
super(CloudFoundryWebEndpointDiscoverer.class);
|
||||||
return (discoverer instanceof CloudFoundryWebAnnotationEndpointDiscoverer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 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.EndpointExtension;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||||
|
@ -33,7 +33,7 @@ import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtensio
|
||||||
* @author Madhura Bhave
|
* @author Madhura Bhave
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
@EndpointExtension(filter = CloudFoundryEndpointFilter.class, endpoint = HealthEndpoint.class)
|
@HealthEndpointCloudFoundryExtension
|
||||||
public class CloudFoundryReactiveHealthEndpointWebExtension {
|
public class CloudFoundryReactiveHealthEndpointWebExtension {
|
||||||
|
|
||||||
private final ReactiveHealthEndpointWebExtension delegate;
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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
|
* @author Madhura Bhave
|
||||||
*/
|
*/
|
||||||
class ReactiveCloudFoundrySecurityInterceptor {
|
class CloudFoundrySecurityInterceptor {
|
||||||
|
|
||||||
private static final Log logger = LogFactory
|
private static final Log logger = LogFactory
|
||||||
.getLog(ReactiveCloudFoundrySecurityInterceptor.class);
|
.getLog(CloudFoundrySecurityInterceptor.class);
|
||||||
|
|
||||||
private final ReactiveTokenValidator tokenValidator;
|
private final ReactiveTokenValidator tokenValidator;
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ class ReactiveCloudFoundrySecurityInterceptor {
|
||||||
|
|
||||||
private static Mono<SecurityResponse> SUCCESS = Mono.just(SecurityResponse.success());
|
private static Mono<SecurityResponse> SUCCESS = Mono.just(SecurityResponse.success());
|
||||||
|
|
||||||
ReactiveCloudFoundrySecurityInterceptor(ReactiveTokenValidator tokenValidator,
|
CloudFoundrySecurityInterceptor(ReactiveTokenValidator tokenValidator,
|
||||||
ReactiveCloudFoundrySecurityService cloudFoundrySecurityService,
|
ReactiveCloudFoundrySecurityService cloudFoundrySecurityService,
|
||||||
String applicationId) {
|
String applicationId) {
|
||||||
this.tokenValidator = tokenValidator;
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
package org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
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.AccessLevel;
|
||||||
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse;
|
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.EndpointLinksResolver;
|
||||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
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.Link;
|
||||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
|
||||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||||
import org.springframework.boot.actuate.endpoint.web.reactive.AbstractWebFluxEndpointHandlerMapping;
|
import org.springframework.boot.actuate.endpoint.web.reactive.AbstractWebFluxEndpointHandlerMapping;
|
||||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
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.bind.annotation.ResponseBody;
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
import org.springframework.web.reactive.HandlerMapping;
|
|
||||||
import org.springframework.web.reactive.result.method.RequestMappingInfoHandlerMapping;
|
import org.springframework.web.reactive.result.method.RequestMappingInfoHandlerMapping;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
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.
|
* Cloud Foundry specific URLs over HTTP using Spring WebFlux.
|
||||||
*
|
*
|
||||||
* @author Madhura Bhave
|
* @author Madhura Bhave
|
||||||
|
* @author Phillip Webb
|
||||||
*/
|
*/
|
||||||
class CloudFoundryWebFluxEndpointHandlerMapping
|
class CloudFoundryWebFluxEndpointHandlerMapping
|
||||||
extends AbstractWebFluxEndpointHandlerMapping {
|
extends AbstractWebFluxEndpointHandlerMapping {
|
||||||
|
|
||||||
private final Method handleRead = ReflectionUtils
|
private final CloudFoundrySecurityInterceptor securityInterceptor;
|
||||||
.findMethod(ReadOperationHandler.class, "handle", ServerWebExchange.class);
|
|
||||||
|
|
||||||
private final Method handleWrite = ReflectionUtils.findMethod(
|
private final EndpointLinksResolver linksResolver = new EndpointLinksResolver();
|
||||||
WriteOperationHandler.class, "handle", ServerWebExchange.class, Map.class);
|
|
||||||
|
|
||||||
private final Method links = ReflectionUtils.findMethod(getClass(), "links",
|
CloudFoundryWebFluxEndpointHandlerMapping(EndpointMapping endpointMapping,
|
||||||
ServerWebExchange.class);
|
Collection<ExposableWebEndpoint> endpoints,
|
||||||
|
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration,
|
||||||
private final EndpointLinksResolver endpointLinksResolver = new EndpointLinksResolver();
|
CloudFoundrySecurityInterceptor securityInterceptor) {
|
||||||
|
super(endpointMapping, endpoints, endpointMediaTypes, corsConfiguration);
|
||||||
private final ReactiveCloudFoundrySecurityInterceptor securityInterceptor;
|
this.securityInterceptor = securityInterceptor;
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Method getLinks() {
|
|
||||||
return this.links;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void registerMappingForOperation(WebOperation operation) {
|
protected ReactiveWebOperation wrapReactiveWebOperation(ExposableWebEndpoint endpoint,
|
||||||
OperationType operationType = operation.getType();
|
WebOperation operation, ReactiveWebOperation reactiveWebOperation) {
|
||||||
OperationInvoker operationInvoker = operation.getInvoker();
|
return new SecureReactiveWebOperation(reactiveWebOperation,
|
||||||
if (operation.isBlocking()) {
|
this.securityInterceptor, endpoint.getId());
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
private Publisher<ResponseEntity<Object>> links(ServerWebExchange exchange) {
|
protected Publisher<ResponseEntity<Object>> links(ServerWebExchange exchange) {
|
||||||
ServerHttpRequest request = exchange.getRequest();
|
ServerHttpRequest request = exchange.getRequest();
|
||||||
return this.securityInterceptor.preHandle(exchange, "")
|
return this.securityInterceptor.preHandle(exchange, "")
|
||||||
.map((securityResponse) -> {
|
.map((securityResponse) -> {
|
||||||
|
@ -105,7 +82,7 @@ class CloudFoundryWebFluxEndpointHandlerMapping
|
||||||
}
|
}
|
||||||
AccessLevel accessLevel = exchange
|
AccessLevel accessLevel = exchange
|
||||||
.getAttribute(AccessLevel.REQUEST_ATTRIBUTE);
|
.getAttribute(AccessLevel.REQUEST_ATTRIBUTE);
|
||||||
Map<String, Link> links = this.endpointLinksResolver
|
Map<String, Link> links = this.linksResolver
|
||||||
.resolveLinks(getEndpoints(), request.getURI().toString());
|
.resolveLinks(getEndpoints(), request.getURI().toString());
|
||||||
return new ResponseEntity<>(
|
return new ResponseEntity<>(
|
||||||
Collections.singletonMap("_links",
|
Collections.singletonMap("_links",
|
||||||
|
@ -126,117 +103,37 @@ class CloudFoundryWebFluxEndpointHandlerMapping
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@code WebEndpointHandlerMapping} that provides mappings for the
|
* {@link ReactiveWebOperation} wrapper to add security.
|
||||||
* 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
|
|
||||||
*/
|
*/
|
||||||
CloudFoundryWebFluxEndpointHandlerMapping(EndpointMapping endpointMapping,
|
private static class SecureReactiveWebOperation implements ReactiveWebOperation {
|
||||||
Collection<EndpointInfo<WebOperation>> webEndpoints,
|
|
||||||
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration,
|
|
||||||
ReactiveCloudFoundrySecurityInterceptor securityInterceptor) {
|
|
||||||
super(endpointMapping, webEndpoints, endpointMediaTypes, corsConfiguration);
|
|
||||||
this.securityInterceptor = securityInterceptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
private final ReactiveWebOperation delegate;
|
||||||
* Base class for handlers for endpoint operations.
|
|
||||||
*/
|
|
||||||
abstract class AbstractOperationHandler {
|
|
||||||
|
|
||||||
private final OperationInvoker operationInvoker;
|
private final CloudFoundrySecurityInterceptor securityInterceptor;
|
||||||
|
|
||||||
private final String endpointId;
|
private final String endpointId;
|
||||||
|
|
||||||
private final ReactiveCloudFoundrySecurityInterceptor securityInterceptor;
|
SecureReactiveWebOperation(ReactiveWebOperation delegate,
|
||||||
|
CloudFoundrySecurityInterceptor securityInterceptor, String endpointId) {
|
||||||
AbstractOperationHandler(OperationInvoker operationInvoker, String endpointId,
|
this.delegate = delegate;
|
||||||
ReactiveCloudFoundrySecurityInterceptor securityInterceptor) {
|
|
||||||
this.operationInvoker = operationInvoker;
|
|
||||||
this.endpointId = endpointId;
|
|
||||||
this.securityInterceptor = securityInterceptor;
|
this.securityInterceptor = securityInterceptor;
|
||||||
|
this.endpointId = endpointId;
|
||||||
}
|
}
|
||||||
|
|
||||||
Publisher<ResponseEntity<Object>> doHandle(ServerWebExchange exchange,
|
@Override
|
||||||
|
public Mono<ResponseEntity<Object>> handle(ServerWebExchange exchange,
|
||||||
Map<String, String> body) {
|
Map<String, String> body) {
|
||||||
return this.securityInterceptor.preHandle(exchange, this.endpointId)
|
return this.securityInterceptor.preHandle(exchange, this.endpointId)
|
||||||
.flatMap((securityResponse) -> flatMapResponse(exchange, body,
|
.flatMap((securityResponse) -> flatMapResponse(exchange, body,
|
||||||
securityResponse));
|
securityResponse));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<? extends ResponseEntity<Object>> flatMapResponse(
|
private Mono<ResponseEntity<Object>> flatMapResponse(ServerWebExchange exchange,
|
||||||
ServerWebExchange exchange, Map<String, String> body,
|
Map<String, String> body, SecurityResponse securityResponse) {
|
||||||
SecurityResponse securityResponse) {
|
|
||||||
if (!securityResponse.getStatus().equals(HttpStatus.OK)) {
|
if (!securityResponse.getStatus().equals(HttpStatus.OK)) {
|
||||||
return Mono.just(new ResponseEntity<>(securityResponse.getStatus()));
|
return Mono.just(new ResponseEntity<>(securityResponse.getStatus()));
|
||||||
}
|
}
|
||||||
Map<String, Object> arguments = new HashMap<>(exchange
|
return this.delegate.handle(exchange, body);
|
||||||
.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,10 @@ import java.util.Collections;
|
||||||
|
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
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.endpoint.condition.ConditionalOnEnabledEndpoint;
|
||||||
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
|
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.EndpointMediaTypes;
|
||||||
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
|
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
|
||||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||||
|
@ -84,27 +84,27 @@ public class ReactiveCloudFoundryActuatorAutoConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public CloudFoundryWebFluxEndpointHandlerMapping cloudFoundryWebFluxEndpointHandlerMapping(
|
public CloudFoundryWebFluxEndpointHandlerMapping cloudFoundryWebFluxEndpointHandlerMapping(
|
||||||
ParameterMapper parameterMapper, EndpointMediaTypes endpointMediaTypes,
|
ParameterValueMapper parameterMapper, EndpointMediaTypes endpointMediaTypes,
|
||||||
WebClient.Builder webClientBuilder) {
|
WebClient.Builder webClientBuilder) {
|
||||||
CloudFoundryWebAnnotationEndpointDiscoverer endpointDiscoverer = new CloudFoundryWebAnnotationEndpointDiscoverer(
|
CloudFoundryWebEndpointDiscoverer endpointDiscoverer = new CloudFoundryWebEndpointDiscoverer(
|
||||||
this.applicationContext, parameterMapper, endpointMediaTypes,
|
this.applicationContext, parameterMapper, endpointMediaTypes,
|
||||||
EndpointPathResolver.useEndpointId(), null, null,
|
EndpointPathResolver.useEndpointId(), Collections.emptyList(),
|
||||||
CloudFoundryReactiveHealthEndpointWebExtension.class);
|
Collections.emptyList());
|
||||||
ReactiveCloudFoundrySecurityInterceptor securityInterceptor = getSecurityInterceptor(
|
CloudFoundrySecurityInterceptor securityInterceptor = getSecurityInterceptor(
|
||||||
webClientBuilder, this.applicationContext.getEnvironment());
|
webClientBuilder, this.applicationContext.getEnvironment());
|
||||||
return new CloudFoundryWebFluxEndpointHandlerMapping(
|
return new CloudFoundryWebFluxEndpointHandlerMapping(
|
||||||
new EndpointMapping("/cloudfoundryapplication"),
|
new EndpointMapping("/cloudfoundryapplication"),
|
||||||
endpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
|
endpointDiscoverer.getEndpoints(), endpointMediaTypes,
|
||||||
getCorsConfiguration(), securityInterceptor);
|
getCorsConfiguration(), securityInterceptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReactiveCloudFoundrySecurityInterceptor getSecurityInterceptor(
|
private CloudFoundrySecurityInterceptor getSecurityInterceptor(
|
||||||
WebClient.Builder restTemplateBuilder, Environment environment) {
|
WebClient.Builder restTemplateBuilder, Environment environment) {
|
||||||
ReactiveCloudFoundrySecurityService cloudfoundrySecurityService = getCloudFoundrySecurityService(
|
ReactiveCloudFoundrySecurityService cloudfoundrySecurityService = getCloudFoundrySecurityService(
|
||||||
restTemplateBuilder, environment);
|
restTemplateBuilder, environment);
|
||||||
ReactiveTokenValidator tokenValidator = new ReactiveTokenValidator(
|
ReactiveTokenValidator tokenValidator = new ReactiveTokenValidator(
|
||||||
cloudfoundrySecurityService);
|
cloudfoundrySecurityService);
|
||||||
return new ReactiveCloudFoundrySecurityInterceptor(tokenValidator,
|
return new CloudFoundrySecurityInterceptor(tokenValidator,
|
||||||
cloudfoundrySecurityService,
|
cloudfoundrySecurityService,
|
||||||
environment.getProperty("vcap.application.application_id"));
|
environment.getProperty("vcap.application.application_id"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,12 +17,13 @@
|
||||||
package org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet;
|
package org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet;
|
||||||
|
|
||||||
import java.util.Arrays;
|
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.endpoint.condition.ConditionalOnEnabledEndpoint;
|
||||||
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
|
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
|
||||||
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
|
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.EndpointMediaTypes;
|
||||||
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
|
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
|
||||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||||
|
@ -85,18 +86,18 @@ public class CloudFoundryActuatorAutoConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandlerMapping(
|
public CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandlerMapping(
|
||||||
ParameterMapper parameterMapper, EndpointMediaTypes endpointMediaTypes,
|
ParameterValueMapper parameterMapper, EndpointMediaTypes endpointMediaTypes,
|
||||||
RestTemplateBuilder restTemplateBuilder) {
|
RestTemplateBuilder restTemplateBuilder) {
|
||||||
CloudFoundryWebAnnotationEndpointDiscoverer endpointDiscoverer = new CloudFoundryWebAnnotationEndpointDiscoverer(
|
CloudFoundryWebEndpointDiscoverer discoverer = new CloudFoundryWebEndpointDiscoverer(
|
||||||
this.applicationContext, parameterMapper, endpointMediaTypes,
|
this.applicationContext, parameterMapper, endpointMediaTypes,
|
||||||
EndpointPathResolver.useEndpointId(), null, null,
|
EndpointPathResolver.useEndpointId(), Collections.emptyList(),
|
||||||
CloudFoundryHealthEndpointWebExtension.class);
|
Collections.emptyList());
|
||||||
CloudFoundrySecurityInterceptor securityInterceptor = getSecurityInterceptor(
|
CloudFoundrySecurityInterceptor securityInterceptor = getSecurityInterceptor(
|
||||||
restTemplateBuilder, this.applicationContext.getEnvironment());
|
restTemplateBuilder, this.applicationContext.getEnvironment());
|
||||||
return new CloudFoundryWebEndpointServletHandlerMapping(
|
return new CloudFoundryWebEndpointServletHandlerMapping(
|
||||||
new EndpointMapping("/cloudfoundryapplication"),
|
new EndpointMapping("/cloudfoundryapplication"),
|
||||||
endpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
|
discoverer.getEndpoints(), endpointMediaTypes, getCorsConfiguration(),
|
||||||
getCorsConfiguration(), securityInterceptor);
|
securityInterceptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CloudFoundrySecurityInterceptor getSecurityInterceptor(
|
private CloudFoundrySecurityInterceptor getSecurityInterceptor(
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet;
|
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.EndpointExtension;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||||
|
@ -31,7 +31,7 @@ import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
|
||||||
* @author Madhura Bhave
|
* @author Madhura Bhave
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
@EndpointExtension(filter = CloudFoundryEndpointFilter.class, endpoint = HealthEndpoint.class)
|
@HealthEndpointCloudFoundryExtension
|
||||||
public class CloudFoundryHealthEndpointWebExtension {
|
public class CloudFoundryHealthEndpointWebExtension {
|
||||||
|
|
||||||
private final HealthEndpointWebExtension delegate;
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -88,12 +88,12 @@ class CloudFoundrySecurityInterceptor {
|
||||||
return SecurityResponse.success();
|
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);
|
Token token = getToken(request);
|
||||||
this.tokenValidator.validate(token);
|
this.tokenValidator.validate(token);
|
||||||
AccessLevel accessLevel = this.cloudFoundrySecurityService
|
AccessLevel accessLevel = this.cloudFoundrySecurityService
|
||||||
.getAccessLevel(token.toString(), this.applicationId);
|
.getAccessLevel(token.toString(), this.applicationId);
|
||||||
if (!accessLevel.isAccessAllowed(path)) {
|
if (!accessLevel.isAccessAllowed(endpointId)) {
|
||||||
throw new CloudFoundryAuthorizationException(Reason.ACCESS_DENIED,
|
throw new CloudFoundryAuthorizationException(Reason.ACCESS_DENIED,
|
||||||
"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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
package org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -28,30 +25,19 @@ import java.util.stream.Collectors;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
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.AccessLevel;
|
||||||
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse;
|
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.EndpointLinksResolver;
|
||||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
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.Link;
|
||||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
|
||||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||||
import org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping;
|
import org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping;
|
||||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
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.bind.annotation.ResponseBody;
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
import org.springframework.web.servlet.HandlerMapping;
|
|
||||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
|
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.
|
* Cloud Foundry specific URLs over HTTP using Spring MVC.
|
||||||
*
|
*
|
||||||
* @author Madhura Bhave
|
* @author Madhura Bhave
|
||||||
|
* @author Phillip Webb
|
||||||
*/
|
*/
|
||||||
class CloudFoundryWebEndpointServletHandlerMapping
|
class CloudFoundryWebEndpointServletHandlerMapping
|
||||||
extends AbstractWebMvcEndpointHandlerMapping {
|
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 CloudFoundrySecurityInterceptor securityInterceptor;
|
||||||
|
|
||||||
private final EndpointLinksResolver endpointLinksResolver = new EndpointLinksResolver();
|
private final EndpointLinksResolver linksResolver = new EndpointLinksResolver();
|
||||||
|
|
||||||
CloudFoundryWebEndpointServletHandlerMapping(EndpointMapping endpointMapping,
|
CloudFoundryWebEndpointServletHandlerMapping(EndpointMapping endpointMapping,
|
||||||
Collection<EndpointInfo<WebOperation>> webEndpoints,
|
Collection<ExposableWebEndpoint> endpoints,
|
||||||
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration,
|
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration,
|
||||||
CloudFoundrySecurityInterceptor securityInterceptor) {
|
CloudFoundrySecurityInterceptor securityInterceptor) {
|
||||||
super(endpointMapping, webEndpoints, endpointMediaTypes, corsConfiguration);
|
super(endpointMapping, endpoints, endpointMediaTypes, corsConfiguration);
|
||||||
this.securityInterceptor = securityInterceptor;
|
this.securityInterceptor = securityInterceptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Method getLinks() {
|
protected ServletWebOperation wrapServletWebOperation(ExposableWebEndpoint endpoint,
|
||||||
return this.links;
|
WebOperation operation, ServletWebOperation servletWebOperation) {
|
||||||
|
return new SecureServletWebOperation(servletWebOperation,
|
||||||
|
this.securityInterceptor, endpoint.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
private Map<String, Map<String, Link>> links(HttpServletRequest request,
|
protected Map<String, Map<String, Link>> links(HttpServletRequest request,
|
||||||
HttpServletResponse response) {
|
HttpServletResponse response) {
|
||||||
SecurityResponse securityResponse = this.securityInterceptor.preHandle(request,
|
SecurityResponse securityResponse = this.securityInterceptor.preHandle(request,
|
||||||
"");
|
"");
|
||||||
|
@ -100,7 +80,7 @@ class CloudFoundryWebEndpointServletHandlerMapping
|
||||||
}
|
}
|
||||||
AccessLevel accessLevel = (AccessLevel) request
|
AccessLevel accessLevel = (AccessLevel) request
|
||||||
.getAttribute(AccessLevel.REQUEST_ATTRIBUTE);
|
.getAttribute(AccessLevel.REQUEST_ATTRIBUTE);
|
||||||
Map<String, Link> links = this.endpointLinksResolver.resolveLinks(getEndpoints(),
|
Map<String, Link> links = this.linksResolver.resolveLinks(getEndpoints(),
|
||||||
request.getRequestURL().toString());
|
request.getRequestURL().toString());
|
||||||
Map<String, Link> filteredLinks = new LinkedHashMap<>();
|
Map<String, Link> filteredLinks = new LinkedHashMap<>();
|
||||||
if (accessLevel == null) {
|
if (accessLevel == null) {
|
||||||
|
@ -120,83 +100,38 @@ class CloudFoundryWebEndpointServletHandlerMapping
|
||||||
securityResponse.getMessage());
|
securityResponse.getMessage());
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
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 ServletWebOperation delegate;
|
||||||
|
|
||||||
private final String endpointId;
|
|
||||||
|
|
||||||
private final CloudFoundrySecurityInterceptor securityInterceptor;
|
private final CloudFoundrySecurityInterceptor securityInterceptor;
|
||||||
|
|
||||||
OperationHandler(OperationInvoker operationInvoker, String id,
|
private final String endpointId;
|
||||||
CloudFoundrySecurityInterceptor securityInterceptor) {
|
|
||||||
this.operationInvoker = operationInvoker;
|
SecureServletWebOperation(ServletWebOperation delegate,
|
||||||
this.endpointId = id;
|
CloudFoundrySecurityInterceptor securityInterceptor, String endpointId) {
|
||||||
|
this.delegate = delegate;
|
||||||
this.securityInterceptor = securityInterceptor;
|
this.securityInterceptor = securityInterceptor;
|
||||||
|
this.endpointId = endpointId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@Override
|
||||||
@ResponseBody
|
public Object handle(HttpServletRequest request, Map<String, String> body) {
|
||||||
public Object handle(HttpServletRequest request,
|
|
||||||
@RequestBody(required = false) Map<String, String> body) {
|
|
||||||
SecurityResponse securityResponse = this.securityInterceptor
|
SecurityResponse securityResponse = this.securityInterceptor
|
||||||
.preHandle(request, this.endpointId);
|
.preHandle(request, this.endpointId);
|
||||||
if (!securityResponse.getStatus().equals(HttpStatus.OK)) {
|
if (!securityResponse.getStatus().equals(HttpStatus.OK)) {
|
||||||
return failureResponse(securityResponse);
|
return new ResponseEntity<Object>(securityResponse.getMessage(),
|
||||||
|
securityResponse.getStatus());
|
||||||
}
|
}
|
||||||
Map<String, Object> arguments = new HashMap<>((Map<String, String>) request
|
return this.delegate.handle(request, body);
|
||||||
.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
package org.springframework.boot.actuate.autoconfigure.endpoint;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.cache.CachingOperationInvokerAdvisor;
|
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
||||||
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper;
|
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
|
||||||
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
|
import org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor;
|
||||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
@ -38,8 +38,9 @@ import org.springframework.core.env.Environment;
|
||||||
public class EndpointAutoConfiguration {
|
public class EndpointAutoConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public ParameterMapper endpointOperationParameterMapper() {
|
@ConditionalOnMissingBean
|
||||||
return new ConversionServiceParameterMapper();
|
public ParameterValueMapper endpointOperationParameterMapper() {
|
||||||
|
return new ConversionServiceParameterValueMapper();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.time.Duration;
|
||||||
import java.util.function.Function;
|
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.BindResult;
|
||||||
import org.springframework.boot.context.properties.bind.Bindable;
|
import org.springframework.boot.context.properties.bind.Bindable;
|
||||||
import org.springframework.boot.context.properties.bind.Binder;
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.Set;
|
||||||
import java.util.stream.Collectors;
|
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.EndpointFilter;
|
||||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.Operation;
|
|
||||||
import org.springframework.boot.context.properties.bind.Bindable;
|
import org.springframework.boot.context.properties.bind.Bindable;
|
||||||
import org.springframework.boot.context.properties.bind.Binder;
|
import org.springframework.boot.context.properties.bind.Binder;
|
||||||
import org.springframework.core.env.Environment;
|
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
|
* {@link EndpointFilter} that will filter endpoints based on {@code expose} and
|
||||||
* {@code exclude} properties.
|
* {@code exclude} properties.
|
||||||
*
|
*
|
||||||
* @param <T> The operation type
|
* @param <E> The endpoint type
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public class ExposeExcludePropertyEndpointFilter<T extends Operation>
|
public class ExposeExcludePropertyEndpointFilter<E extends ExposableEndpoint<?>>
|
||||||
implements EndpointFilter<T> {
|
implements EndpointFilter<E> {
|
||||||
|
|
||||||
private final Class<? extends EndpointDiscoverer<T>> discovererType;
|
private final Class<E> endpointType;
|
||||||
|
|
||||||
private final Set<String> expose;
|
private final Set<String> expose;
|
||||||
|
|
||||||
|
@ -52,25 +50,23 @@ public class ExposeExcludePropertyEndpointFilter<T extends Operation>
|
||||||
|
|
||||||
private final Set<String> exposeDefaults;
|
private final Set<String> exposeDefaults;
|
||||||
|
|
||||||
public ExposeExcludePropertyEndpointFilter(
|
public ExposeExcludePropertyEndpointFilter(Class<E> endpointType,
|
||||||
Class<? extends EndpointDiscoverer<T>> discovererType,
|
|
||||||
Environment environment, String prefix, String... exposeDefaults) {
|
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.notNull(environment, "Environment must not be null");
|
||||||
Assert.hasText(prefix, "Prefix must not be empty");
|
Assert.hasText(prefix, "Prefix must not be empty");
|
||||||
Binder binder = Binder.get(environment);
|
Binder binder = Binder.get(environment);
|
||||||
this.discovererType = discovererType;
|
this.endpointType = endpointType;
|
||||||
this.expose = bind(binder, prefix + ".expose");
|
this.expose = bind(binder, prefix + ".expose");
|
||||||
this.exclude = bind(binder, prefix + ".exclude");
|
this.exclude = bind(binder, prefix + ".exclude");
|
||||||
this.exposeDefaults = asSet(Arrays.asList(exposeDefaults));
|
this.exposeDefaults = asSet(Arrays.asList(exposeDefaults));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExposeExcludePropertyEndpointFilter(
|
public ExposeExcludePropertyEndpointFilter(Class<E> endpointType,
|
||||||
Class<? extends EndpointDiscoverer<T>> discovererType,
|
|
||||||
Collection<String> expose, Collection<String> exclude,
|
Collection<String> expose, Collection<String> exclude,
|
||||||
String... exposeDefaults) {
|
String... exposeDefaults) {
|
||||||
Assert.notNull(discovererType, "Discoverer Type must not be null");
|
Assert.notNull(endpointType, "EndpointType Type must not be null");
|
||||||
this.discovererType = discovererType;
|
this.endpointType = endpointType;
|
||||||
this.expose = asSet(expose);
|
this.expose = asSet(expose);
|
||||||
this.exclude = asSet(exclude);
|
this.exclude = asSet(exclude);
|
||||||
this.exposeDefaults = asSet(Arrays.asList(exposeDefaults));
|
this.exposeDefaults = asSet(Arrays.asList(exposeDefaults));
|
||||||
|
@ -90,30 +86,30 @@ public class ExposeExcludePropertyEndpointFilter<T extends Operation>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean match(EndpointInfo<T> info, EndpointDiscoverer<T> discoverer) {
|
public boolean match(E endpoint) {
|
||||||
if (this.discovererType.isInstance(discoverer)) {
|
if (this.endpointType.isInstance(endpoint)) {
|
||||||
return isExposed(info) && !isExcluded(info);
|
return isExposed(endpoint) && !isExcluded(endpoint);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isExposed(EndpointInfo<T> info) {
|
private boolean isExposed(ExposableEndpoint<?> endpoint) {
|
||||||
if (this.expose.isEmpty()) {
|
if (this.expose.isEmpty()) {
|
||||||
return this.exposeDefaults.contains("*")
|
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()) {
|
if (this.exclude.isEmpty()) {
|
||||||
return false;
|
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) {
|
private boolean contains(Set<String> items, ExposableEndpoint<?> endpoint) {
|
||||||
return items.contains(info.getId().toLowerCase());
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
package org.springframework.boot.actuate.autoconfigure.endpoint.jmx;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.management.MBeanServer;
|
import javax.management.MBeanServer;
|
||||||
import javax.management.MalformedObjectNameException;
|
import javax.management.MalformedObjectNameException;
|
||||||
import javax.management.ObjectName;
|
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.EndpointObjectNameFactory;
|
||||||
|
import org.springframework.boot.actuate.endpoint.jmx.ExposableJmxEndpoint;
|
||||||
import org.springframework.jmx.support.ObjectNameManager;
|
import org.springframework.jmx.support.ObjectNameManager;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
@ -50,15 +48,18 @@ class DefaultEndpointObjectNameFactory implements EndpointObjectNameFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ObjectName generate(EndpointMBean mBean) throws MalformedObjectNameException {
|
public ObjectName getObjectName(ExposableJmxEndpoint endpoint)
|
||||||
String baseObjectName = this.properties.getDomain() + ":type=Endpoint" + ",name="
|
throws MalformedObjectNameException {
|
||||||
+ StringUtils.capitalize(mBean.getEndpointId());
|
StringBuilder builder = new StringBuilder(this.properties.getDomain());
|
||||||
StringBuilder builder = new StringBuilder(baseObjectName);
|
builder.append(":type=Endpoint");
|
||||||
if (this.mBeanServer != null && hasMBean(baseObjectName)) {
|
builder.append(",name=" + StringUtils.capitalize(endpoint.getId()));
|
||||||
builder.append(",context=").append(this.contextId);
|
String baseName = builder.toString();
|
||||||
|
if (this.mBeanServer != null && hasMBean(baseName)) {
|
||||||
|
builder.append(",context=" + this.contextId);
|
||||||
}
|
}
|
||||||
if (this.properties.isUniqueNames()) {
|
if (this.properties.isUniqueNames()) {
|
||||||
builder.append(",identity=").append(ObjectUtils.getIdentityHexString(mBean));
|
String identity = ObjectUtils.getIdentityHexString(endpoint);
|
||||||
|
builder.append(",identity=" + identity);
|
||||||
}
|
}
|
||||||
builder.append(getStaticNames());
|
builder.append(getStaticNames());
|
||||||
return ObjectNameManager.getInstance(builder.toString());
|
return ObjectNameManager.getInstance(builder.toString());
|
||||||
|
@ -74,10 +75,8 @@ class DefaultEndpointObjectNameFactory implements EndpointObjectNameFactory {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
for (Map.Entry<Object, Object> name : this.properties.getStaticNames()
|
this.properties.getStaticNames()
|
||||||
.entrySet()) {
|
.forEach((name, value) -> builder.append("," + name + "=" + value));
|
||||||
builder.append(",").append(name.getKey()).append("=").append(name.getValue());
|
|
||||||
}
|
|
||||||
return builder.toString();
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
package org.springframework.boot.actuate.autoconfigure.endpoint.jmx;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.management.MBeanServer;
|
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.autoconfigure.endpoint.ExposeExcludePropertyEndpointFilter;
|
||||||
import org.springframework.boot.actuate.endpoint.EndpointFilter;
|
import org.springframework.boot.actuate.endpoint.EndpointFilter;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
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.EndpointObjectNameFactory;
|
||||||
import org.springframework.boot.actuate.endpoint.jmx.JmxOperation;
|
import org.springframework.boot.actuate.endpoint.jmx.ExposableJmxEndpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxAnnotationEndpointDiscoverer;
|
import org.springframework.boot.actuate.endpoint.jmx.JacksonJmxOperationResponseMapper;
|
||||||
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInvokerAdvisor;
|
import org.springframework.boot.actuate.endpoint.jmx.JmxEndpointExporter;
|
||||||
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
|
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.AutoConfigureAfter;
|
||||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
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.ConditionalOnProperty;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
|
||||||
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
|
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
|
||||||
|
@ -66,34 +72,37 @@ public class JmxEndpointAutoConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public JmxAnnotationEndpointDiscoverer jmxAnnotationEndpointDiscoverer(
|
@ConditionalOnMissingBean(JmxEndpointsSupplier.class)
|
||||||
ParameterMapper parameterMapper,
|
public JmxEndpointDiscoverer jmxAnnotationEndpointDiscoverer(
|
||||||
Collection<OperationMethodInvokerAdvisor> invokerAdvisors,
|
ParameterValueMapper parameterValueMapper,
|
||||||
Collection<EndpointFilter<JmxOperation>> filters) {
|
ObjectProvider<Collection<OperationInvokerAdvisor>> invokerAdvisors,
|
||||||
return new JmxAnnotationEndpointDiscoverer(this.applicationContext,
|
ObjectProvider<Collection<EndpointFilter<ExposableJmxEndpoint>>> filters) {
|
||||||
parameterMapper, invokerAdvisors, filters);
|
return new JmxEndpointDiscoverer(this.applicationContext, parameterValueMapper,
|
||||||
|
invokerAdvisors.getIfAvailable(Collections::emptyList),
|
||||||
|
filters.getIfAvailable(Collections::emptyList));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnSingleCandidate(MBeanServer.class)
|
@ConditionalOnSingleCandidate(MBeanServer.class)
|
||||||
public JmxEndpointExporter jmxMBeanExporter(
|
public JmxEndpointExporter jmxMBeanExporter(MBeanServer mBeanServer,
|
||||||
JmxAnnotationEndpointDiscoverer jmxAnnotationEndpointDiscoverer,
|
ObjectProvider<ObjectMapper> objectMapper,
|
||||||
MBeanServer mBeanServer, JmxAnnotationEndpointDiscoverer endpointDiscoverer,
|
JmxEndpointsSupplier jmxEndpointsSupplier) {
|
||||||
ObjectProvider<ObjectMapper> objectMapper) {
|
String contextId = ObjectUtils.getIdentityHexString(this.applicationContext);
|
||||||
EndpointObjectNameFactory objectNameFactory = new DefaultEndpointObjectNameFactory(
|
EndpointObjectNameFactory objectNameFactory = new DefaultEndpointObjectNameFactory(
|
||||||
this.properties, mBeanServer,
|
this.properties, mBeanServer, contextId);
|
||||||
ObjectUtils.getIdentityHexString(this.applicationContext));
|
JmxOperationResponseMapper responseMapper = new JacksonJmxOperationResponseMapper(
|
||||||
EndpointMBeanRegistrar registrar = new EndpointMBeanRegistrar(mBeanServer,
|
objectMapper.getIfAvailable());
|
||||||
objectNameFactory);
|
return new JmxEndpointExporter(mBeanServer, objectNameFactory, responseMapper,
|
||||||
return new JmxEndpointExporter(jmxAnnotationEndpointDiscoverer, registrar,
|
jmxEndpointsSupplier.getEndpoints());
|
||||||
objectMapper.getIfAvailable(ObjectMapper::new));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public ExposeExcludePropertyEndpointFilter<JmxOperation> jmxIncludeExcludePropertyEndpointFilter() {
|
public ExposeExcludePropertyEndpointFilter<ExposableJmxEndpoint> jmxIncludeExcludePropertyEndpointFilter() {
|
||||||
return new ExposeExcludePropertyEndpointFilter<>(
|
Set<String> expose = this.properties.getExpose();
|
||||||
JmxAnnotationEndpointDiscoverer.class, this.properties.getExpose(),
|
Set<String> exclude = this.properties.getExclude();
|
||||||
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
package org.springframework.boot.actuate.autoconfigure.endpoint.web;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
|
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
|
||||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,33 +34,28 @@ public class DefaultEndpointPathProvider implements EndpointPathProvider {
|
||||||
|
|
||||||
private final String basePath;
|
private final String basePath;
|
||||||
|
|
||||||
private final EndpointDiscoverer<WebOperation> endpointDiscoverer;
|
private final Collection<ExposableWebEndpoint> endpoints;
|
||||||
|
|
||||||
public DefaultEndpointPathProvider(
|
public DefaultEndpointPathProvider(WebEndpointProperties webEndpointProperties,
|
||||||
EndpointDiscoverer<WebOperation> endpointDiscoverer,
|
Collection<? extends ExposableWebEndpoint> endpoints) {
|
||||||
WebEndpointProperties webEndpointProperties) {
|
|
||||||
this.endpointDiscoverer = endpointDiscoverer;
|
|
||||||
this.basePath = webEndpointProperties.getBasePath();
|
this.basePath = webEndpointProperties.getBasePath();
|
||||||
|
this.endpoints = Collections.unmodifiableCollection(endpoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getPaths() {
|
public List<String> getPaths() {
|
||||||
return getEndpoints().map(this::getPath).collect(Collectors.toList());
|
return this.endpoints.stream().map(this::getPath).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getPath(String id) {
|
public String getPath(String id) {
|
||||||
Assert.notNull(id, "ID must not be null");
|
Assert.notNull(id, "ID must not be null");
|
||||||
return getEndpoints().filter((info) -> id.equals(info.getId())).findFirst()
|
return this.endpoints.stream().filter((info) -> id.equals(info.getId()))
|
||||||
.map(this::getPath).orElse(null);
|
.findFirst().map(this::getPath).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream<EndpointInfo<WebOperation>> getEndpoints() {
|
private String getPath(ExposableWebEndpoint endpoint) {
|
||||||
return this.endpointDiscoverer.discoverEndpoints().stream();
|
return this.basePath + "/" + endpoint.getId();
|
||||||
}
|
|
||||||
|
|
||||||
private String getPath(EndpointInfo<WebOperation> endpointInfo) {
|
|
||||||
return this.basePath + "/" + endpointInfo.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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
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.EndpointAutoConfiguration;
|
||||||
import org.springframework.boot.actuate.autoconfigure.endpoint.ExposeExcludePropertyEndpointFilter;
|
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.EndpointFilter;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
|
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
|
||||||
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInvokerAdvisor;
|
import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor;
|
||||||
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.EndpointMediaTypes;
|
||||||
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
|
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
|
||||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
|
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.AutoConfigureAfter;
|
||||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
|
@ -76,17 +79,6 @@ public class WebEndpointAutoConfiguration {
|
||||||
return new DefaultEndpointPathResolver(this.properties.getPathMapping());
|
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
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public EndpointMediaTypes endpointMediaTypes() {
|
public EndpointMediaTypes endpointMediaTypes() {
|
||||||
|
@ -94,18 +86,34 @@ public class WebEndpointAutoConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean(WebEndpointsSupplier.class)
|
||||||
public EndpointPathProvider endpointPathProvider(
|
public WebEndpointDiscoverer webEndpointDiscoverer(
|
||||||
EndpointDiscoverer<WebOperation> endpointDiscoverer,
|
ParameterValueMapper parameterValueMapper,
|
||||||
WebEndpointProperties webEndpointProperties) {
|
EndpointMediaTypes endpointMediaTypes,
|
||||||
return new DefaultEndpointPathProvider(endpointDiscoverer, webEndpointProperties);
|
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
|
@Bean
|
||||||
public ExposeExcludePropertyEndpointFilter<WebOperation> webIncludeExcludePropertyEndpointFilter() {
|
@ConditionalOnMissingBean
|
||||||
return new ExposeExcludePropertyEndpointFilter<>(
|
public EndpointPathProvider endpointPathProvider(
|
||||||
WebAnnotationEndpointDiscoverer.class, this.properties.getExpose(),
|
WebEndpointsSupplier webEndpointsSupplier,
|
||||||
this.properties.getExclude(), "info", "health");
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
package org.springframework.boot.actuate.autoconfigure.endpoint.web.jersey;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
|
||||||
import org.glassfish.jersey.server.ResourceConfig;
|
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.autoconfigure.web.ManagementContextConfiguration;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
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.actuate.endpoint.web.jersey.JerseyEndpointResourceFactory;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
@ -45,19 +48,25 @@ import org.springframework.context.annotation.Configuration;
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConditionalOnWebApplication(type = Type.SERVLET)
|
@ConditionalOnWebApplication(type = Type.SERVLET)
|
||||||
@ConditionalOnClass(ResourceConfig.class)
|
@ConditionalOnClass(ResourceConfig.class)
|
||||||
@ConditionalOnBean({ ResourceConfig.class, WebAnnotationEndpointDiscoverer.class })
|
@ConditionalOnBean({ ResourceConfig.class, WebEndpointsSupplier.class })
|
||||||
@ConditionalOnMissingBean(type = "org.springframework.web.servlet.DispatcherServlet")
|
@ConditionalOnMissingBean(type = "org.springframework.web.servlet.DispatcherServlet")
|
||||||
class JerseyWebEndpointManagementContextConfiguration {
|
class JerseyWebEndpointManagementContextConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public ResourceConfigCustomizer webEndpointRegistrar(
|
public ResourceConfigCustomizer webEndpointRegistrar(
|
||||||
WebAnnotationEndpointDiscoverer endpointDiscoverer,
|
WebEndpointsSupplier webEndpointsSupplier,
|
||||||
EndpointMediaTypes endpointMediaTypes,
|
EndpointMediaTypes endpointMediaTypes,
|
||||||
WebEndpointProperties webEndpointProperties) {
|
WebEndpointProperties webEndpointProperties) {
|
||||||
return (resourceConfig) -> resourceConfig.registerResources(
|
return (resourceConfig) -> {
|
||||||
new HashSet<>(new JerseyEndpointResourceFactory().createEndpointResources(
|
JerseyEndpointResourceFactory resourceFactory = new JerseyEndpointResourceFactory();
|
||||||
new EndpointMapping(webEndpointProperties.getBasePath()),
|
String basePath = webEndpointProperties.getBasePath();
|
||||||
endpointDiscoverer.discoverEndpoints(), endpointMediaTypes)));
|
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.autoconfigure.web.ManagementContextConfiguration;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
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.actuate.endpoint.web.reactive.WebFluxEndpointHandlerMapping;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
@ -44,19 +44,20 @@ import org.springframework.web.reactive.DispatcherHandler;
|
||||||
@ManagementContextConfiguration
|
@ManagementContextConfiguration
|
||||||
@ConditionalOnWebApplication(type = Type.REACTIVE)
|
@ConditionalOnWebApplication(type = Type.REACTIVE)
|
||||||
@ConditionalOnClass({ DispatcherHandler.class, HttpHandler.class })
|
@ConditionalOnClass({ DispatcherHandler.class, HttpHandler.class })
|
||||||
@ConditionalOnBean(WebAnnotationEndpointDiscoverer.class)
|
@ConditionalOnBean(WebEndpointsSupplier.class)
|
||||||
@EnableConfigurationProperties(CorsEndpointProperties.class)
|
@EnableConfigurationProperties(CorsEndpointProperties.class)
|
||||||
public class WebFluxEndpointManagementContextConfiguration {
|
public class WebFluxEndpointManagementContextConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public WebFluxEndpointHandlerMapping webEndpointReactiveHandlerMapping(
|
public WebFluxEndpointHandlerMapping webEndpointReactiveHandlerMapping(
|
||||||
WebAnnotationEndpointDiscoverer endpointDiscoverer,
|
WebEndpointsSupplier webEndpointsSupplier,
|
||||||
EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,
|
EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,
|
||||||
WebEndpointProperties webEndpointProperties) {
|
WebEndpointProperties webEndpointProperties) {
|
||||||
return new WebFluxEndpointHandlerMapping(
|
EndpointMapping endpointMapping = new EndpointMapping(
|
||||||
new EndpointMapping(webEndpointProperties.getBasePath()),
|
webEndpointProperties.getBasePath());
|
||||||
endpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
|
return new WebFluxEndpointHandlerMapping(endpointMapping,
|
||||||
|
webEndpointsSupplier.getEndpoints(), endpointMediaTypes,
|
||||||
corsProperties.toCorsConfiguration());
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.autoconfigure.web.ManagementContextConfiguration;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
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.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
@ -40,25 +40,24 @@ import org.springframework.web.servlet.DispatcherServlet;
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ManagementContextConfiguration
|
@ManagementContextConfiguration
|
||||||
@ConditionalOnWebApplication(type = Type.SERVLET)
|
@ConditionalOnWebApplication(type = Type.SERVLET)
|
||||||
@ConditionalOnClass(DispatcherServlet.class)
|
@ConditionalOnClass(DispatcherServlet.class)
|
||||||
@ConditionalOnBean({ DispatcherServlet.class, WebAnnotationEndpointDiscoverer.class })
|
@ConditionalOnBean({ DispatcherServlet.class, WebEndpointsSupplier.class })
|
||||||
@EnableConfigurationProperties(CorsEndpointProperties.class)
|
@EnableConfigurationProperties(CorsEndpointProperties.class)
|
||||||
public class WebMvcEndpointManagementContextConfiguration {
|
public class WebMvcEndpointManagementContextConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(
|
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(
|
||||||
WebAnnotationEndpointDiscoverer endpointDiscoverer,
|
WebEndpointsSupplier webEndpointsSupplier,
|
||||||
EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,
|
EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,
|
||||||
WebEndpointProperties webEndpointProperties) {
|
WebEndpointProperties webEndpointProperties) {
|
||||||
WebMvcEndpointHandlerMapping handlerMapping = new WebMvcEndpointHandlerMapping(
|
EndpointMapping endpointMapping = new EndpointMapping(
|
||||||
new EndpointMapping(webEndpointProperties.getBasePath()),
|
webEndpointProperties.getBasePath());
|
||||||
endpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
|
return new WebMvcEndpointHandlerMapping(endpointMapping,
|
||||||
|
webEndpointsSupplier.getEndpoints(), endpointMediaTypes,
|
||||||
corsProperties.toCorsConfiguration());
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
package org.springframework.boot.actuate.autoconfigure.cloudfoundry;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.Mockito;
|
|
||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.EndpointFilter;
|
import org.springframework.boot.actuate.endpoint.annotation.DiscoveredEndpoint;
|
||||||
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 static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link CloudFoundryEndpointFilter}.
|
* Tests for {@link CloudFoundryEndpointFilter}.
|
||||||
|
@ -40,38 +31,22 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||||
*/
|
*/
|
||||||
public class CloudFoundryEndpointFilterTests {
|
public class CloudFoundryEndpointFilterTests {
|
||||||
|
|
||||||
private CloudFoundryEndpointFilter filter;
|
private CloudFoundryEndpointFilter filter = new CloudFoundryEndpointFilter();
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
this.filter = new CloudFoundryEndpointFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void matchIfDiscovererCloudFoundryShouldReturnFalse() {
|
public void matchIfDiscovererCloudFoundryShouldReturnFalse() {
|
||||||
CloudFoundryWebAnnotationEndpointDiscoverer discoverer = Mockito
|
DiscoveredEndpoint<?> endpoint = mock(DiscoveredEndpoint.class);
|
||||||
.mock(CloudFoundryWebAnnotationEndpointDiscoverer.class);
|
given(endpoint.wasDiscoveredBy(CloudFoundryWebEndpointDiscoverer.class))
|
||||||
assertThat(this.filter.match(null, discoverer)).isTrue();
|
.willReturn(true);
|
||||||
|
assertThat(this.filter.match(endpoint)).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void matchIfDiscovererNotCloudFoundryShouldReturnFalse() {
|
public void matchIfDiscovererNotCloudFoundryShouldReturnFalse() {
|
||||||
WebAnnotationEndpointDiscoverer discoverer = Mockito
|
DiscoveredEndpoint<?> endpoint = mock(DiscoveredEndpoint.class);
|
||||||
.mock(WebAnnotationEndpointDiscoverer.class);
|
given(endpoint.wasDiscoveredBy(CloudFoundryWebEndpointDiscoverer.class))
|
||||||
assertThat(this.filter.match(null, discoverer)).isFalse();
|
.willReturn(false);
|
||||||
}
|
assertThat(this.filter.match(endpoint)).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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.junit.Test;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||||
import org.springframework.boot.actuate.endpoint.cache.CachingOperationInvokerAdvisor;
|
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
|
||||||
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper;
|
import org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor;
|
||||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||||
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
|
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.WebOperation;
|
||||||
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
|
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
|
||||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
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.AnnotationConfigApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.core.convert.support.DefaultConversionService;
|
import org.springframework.core.convert.support.DefaultConversionService;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
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
|
* @author Madhura Bhave
|
||||||
*/
|
*/
|
||||||
public class CloudFoundryWebAnnotationEndpointDiscovererTests {
|
public class CloudFoundryWebEndpointDiscovererTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void discovererShouldAddSuppliedExtensionForHealthEndpoint() {
|
public void getEndpointsShouldAddCloudFoundryHealthExtension() {
|
||||||
load(TestConfiguration.class, (endpointDiscoverer) -> {
|
load(TestConfiguration.class, (discoverer) -> {
|
||||||
Collection<EndpointInfo<WebOperation>> endpoints = endpointDiscoverer
|
Collection<ExposableWebEndpoint> endpoints = discoverer.getEndpoints();
|
||||||
.discoverEndpoints();
|
|
||||||
assertThat(endpoints.size()).isEqualTo(2);
|
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,
|
private void load(Class<?> configuration,
|
||||||
Consumer<CloudFoundryWebAnnotationEndpointDiscoverer> consumer) {
|
Consumer<CloudFoundryWebEndpointDiscoverer> consumer) {
|
||||||
this.load((id) -> null, (id) -> id, configuration, consumer);
|
this.load((id) -> null, (id) -> id, configuration, consumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void load(Function<String, Long> timeToLive,
|
private void load(Function<String, Long> timeToLive,
|
||||||
EndpointPathResolver endpointPathResolver, Class<?> configuration,
|
EndpointPathResolver endpointPathResolver, Class<?> configuration,
|
||||||
Consumer<CloudFoundryWebAnnotationEndpointDiscoverer> consumer) {
|
Consumer<CloudFoundryWebEndpointDiscoverer> consumer) {
|
||||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||||
configuration);
|
configuration);
|
||||||
try {
|
try {
|
||||||
ConversionServiceParameterMapper parameterMapper = new ConversionServiceParameterMapper(
|
ConversionServiceParameterValueMapper parameterMapper = new ConversionServiceParameterValueMapper(
|
||||||
DefaultConversionService.getSharedInstance());
|
DefaultConversionService.getSharedInstance());
|
||||||
EndpointMediaTypes mediaTypes = new EndpointMediaTypes(
|
EndpointMediaTypes mediaTypes = new EndpointMediaTypes(
|
||||||
Collections.singletonList("application/json"),
|
Collections.singletonList("application/json"),
|
||||||
Collections.singletonList("application/json"));
|
Collections.singletonList("application/json"));
|
||||||
CloudFoundryWebAnnotationEndpointDiscoverer discoverer = new CloudFoundryWebAnnotationEndpointDiscoverer(
|
CloudFoundryWebEndpointDiscoverer discoverer = new CloudFoundryWebEndpointDiscoverer(
|
||||||
context, parameterMapper, mediaTypes, endpointPathResolver,
|
context, parameterMapper, mediaTypes, endpointPathResolver,
|
||||||
Collections.singleton(new CachingOperationInvokerAdvisor(timeToLive)),
|
Collections.singleton(new CachingOperationInvokerAdvisor(timeToLive)),
|
||||||
null, HealthWebEndpointExtension.class);
|
Collections.emptyList());
|
||||||
consumer.accept(discoverer);
|
consumer.accept(discoverer);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
@ -91,34 +98,24 @@ public class CloudFoundryWebAnnotationEndpointDiscovererTests {
|
||||||
return new TestEndpoint();
|
return new TestEndpoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TestEndpointWebExtension testEndpointWebExtension() {
|
||||||
|
return new TestEndpointWebExtension();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public HealthEndpoint healthEndpoint() {
|
public HealthEndpoint healthEndpoint() {
|
||||||
return new HealthEndpoint(null);
|
return new HealthEndpoint(mock(HealthIndicator.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public TestWebEndpointExtension testEndpointExtension() {
|
public HealthEndpointWebExtension healthEndpointWebExtension() {
|
||||||
return new TestWebEndpointExtension();
|
return new HealthEndpointWebExtension();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public HealthWebEndpointExtension healthEndpointExtension() {
|
public TestHealthEndpointCloudFoundryExtension testHealthEndpointCloudFoundryExtension() {
|
||||||
return new HealthWebEndpointExtension();
|
return new TestHealthEndpointCloudFoundryExtension();
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public OtherHealthWebEndpointExtension otherHealthEndpointExtension() {
|
|
||||||
return new OtherHealthWebEndpointExtension();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@EndpointWebExtension(endpoint = TestEndpoint.class)
|
|
||||||
static class TestWebEndpointExtension {
|
|
||||||
|
|
||||||
@ReadOperation
|
|
||||||
public Object getAll() {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -133,8 +130,8 @@ public class CloudFoundryWebAnnotationEndpointDiscovererTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@EndpointWebExtension(endpoint = HealthEndpoint.class)
|
@EndpointWebExtension(endpoint = TestEndpoint.class)
|
||||||
static class HealthWebEndpointExtension {
|
static class TestEndpointWebExtension {
|
||||||
|
|
||||||
@ReadOperation
|
@ReadOperation
|
||||||
public Object getAll() {
|
public Object getAll() {
|
||||||
|
@ -144,7 +141,7 @@ public class CloudFoundryWebAnnotationEndpointDiscovererTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@EndpointWebExtension(endpoint = HealthEndpoint.class)
|
@EndpointWebExtension(endpoint = HealthEndpoint.class)
|
||||||
static class OtherHealthWebEndpointExtension {
|
static class HealthEndpointWebExtension {
|
||||||
|
|
||||||
@ReadOperation
|
@ReadOperation
|
||||||
public Object getAll() {
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.AccessLevel;
|
||||||
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException;
|
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException;
|
||||||
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException.Reason;
|
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.Endpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
|
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
|
||||||
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper;
|
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
||||||
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
|
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
|
||||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||||
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
|
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
|
||||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
|
||||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
|
|
||||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||||
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
|
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
|
||||||
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
|
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
|
||||||
|
@ -205,9 +203,9 @@ public class CloudFoundryWebFluxEndpointIntegrationTests {
|
||||||
private int port;
|
private int port;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public ReactiveCloudFoundrySecurityInterceptor interceptor() {
|
public CloudFoundrySecurityInterceptor interceptor() {
|
||||||
return new ReactiveCloudFoundrySecurityInterceptor(tokenValidator,
|
return new CloudFoundrySecurityInterceptor(tokenValidator, securityService,
|
||||||
securityService, "app-id");
|
"app-id");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
@ -218,27 +216,27 @@ public class CloudFoundryWebFluxEndpointIntegrationTests {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public CloudFoundryWebFluxEndpointHandlerMapping cloudFoundryWebEndpointServletHandlerMapping(
|
public CloudFoundryWebFluxEndpointHandlerMapping cloudFoundryWebEndpointServletHandlerMapping(
|
||||||
EndpointDiscoverer<WebOperation> webEndpointDiscoverer,
|
WebEndpointDiscoverer webEndpointDiscoverer,
|
||||||
EndpointMediaTypes endpointMediaTypes,
|
EndpointMediaTypes endpointMediaTypes,
|
||||||
ReactiveCloudFoundrySecurityInterceptor interceptor) {
|
CloudFoundrySecurityInterceptor interceptor) {
|
||||||
CorsConfiguration corsConfiguration = new CorsConfiguration();
|
CorsConfiguration corsConfiguration = new CorsConfiguration();
|
||||||
corsConfiguration.setAllowedOrigins(Arrays.asList("http://example.com"));
|
corsConfiguration.setAllowedOrigins(Arrays.asList("http://example.com"));
|
||||||
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST"));
|
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST"));
|
||||||
return new CloudFoundryWebFluxEndpointHandlerMapping(
|
return new CloudFoundryWebFluxEndpointHandlerMapping(
|
||||||
new EndpointMapping("/cfApplication"),
|
new EndpointMapping("/cfApplication"),
|
||||||
webEndpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
|
webEndpointDiscoverer.getEndpoints(), endpointMediaTypes,
|
||||||
corsConfiguration, interceptor);
|
corsConfiguration, interceptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public WebAnnotationEndpointDiscoverer webEndpointDiscoverer(
|
public WebEndpointDiscoverer webEndpointDiscoverer(
|
||||||
ApplicationContext applicationContext,
|
ApplicationContext applicationContext,
|
||||||
EndpointMediaTypes endpointMediaTypes) {
|
EndpointMediaTypes endpointMediaTypes) {
|
||||||
ParameterMapper parameterMapper = new ConversionServiceParameterMapper(
|
ParameterValueMapper parameterMapper = new ConversionServiceParameterValueMapper(
|
||||||
DefaultConversionService.getSharedInstance());
|
DefaultConversionService.getSharedInstance());
|
||||||
return new WebAnnotationEndpointDiscoverer(applicationContext,
|
return new WebEndpointDiscoverer(applicationContext, parameterMapper,
|
||||||
parameterMapper, endpointMediaTypes,
|
endpointMediaTypes, EndpointPathResolver.useEndpointId(),
|
||||||
EndpointPathResolver.useEndpointId(), null, null);
|
Collections.emptyList(), Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@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.endpoint.web.WebEndpointAutoConfiguration;
|
||||||
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
|
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
|
||||||
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
|
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.Endpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||||
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
|
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.actuate.endpoint.web.WebOperation;
|
||||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
|
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
|
||||||
|
@ -199,9 +199,8 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests {
|
||||||
this.context.register(TestConfiguration.class);
|
this.context.register(TestConfiguration.class);
|
||||||
this.context.refresh();
|
this.context.refresh();
|
||||||
CloudFoundryWebFluxEndpointHandlerMapping handlerMapping = getHandlerMapping();
|
CloudFoundryWebFluxEndpointHandlerMapping handlerMapping = getHandlerMapping();
|
||||||
List<EndpointInfo<WebOperation>> endpoints = (List<EndpointInfo<WebOperation>>) handlerMapping
|
Collection<ExposableWebEndpoint> endpoints = handlerMapping.getEndpoints();
|
||||||
.getEndpoints();
|
List<String> endpointIds = endpoints.stream().map(ExposableEndpoint::getId)
|
||||||
List<String> endpointIds = endpoints.stream().map(EndpointInfo::getId)
|
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
assertThat(endpointIds).contains("test");
|
assertThat(endpointIds).contains("test");
|
||||||
}
|
}
|
||||||
|
@ -212,9 +211,8 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests {
|
||||||
this.context.register(TestConfiguration.class);
|
this.context.register(TestConfiguration.class);
|
||||||
this.context.refresh();
|
this.context.refresh();
|
||||||
CloudFoundryWebFluxEndpointHandlerMapping handlerMapping = getHandlerMapping();
|
CloudFoundryWebFluxEndpointHandlerMapping handlerMapping = getHandlerMapping();
|
||||||
List<EndpointInfo<WebOperation>> endpoints = (List<EndpointInfo<WebOperation>>) handlerMapping
|
Collection<ExposableWebEndpoint> endpoints = handlerMapping.getEndpoints();
|
||||||
.getEndpoints();
|
ExposableWebEndpoint endpoint = endpoints.stream()
|
||||||
EndpointInfo<WebOperation> endpoint = endpoints.stream()
|
|
||||||
.filter((candidate) -> "test".equals(candidate.getId())).findFirst()
|
.filter((candidate) -> "test".equals(candidate.getId())).findFirst()
|
||||||
.get();
|
.get();
|
||||||
assertThat(endpoint.getOperations()).hasSize(1);
|
assertThat(endpoint.getOperations()).hasSize(1);
|
||||||
|
@ -226,12 +224,10 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests {
|
||||||
public void healthEndpointInvokerShouldBeCloudFoundryWebExtension() {
|
public void healthEndpointInvokerShouldBeCloudFoundryWebExtension() {
|
||||||
setupContextWithCloudEnabled();
|
setupContextWithCloudEnabled();
|
||||||
this.context.refresh();
|
this.context.refresh();
|
||||||
Collection<EndpointInfo<WebOperation>> endpoints = getHandlerMapping()
|
Collection<ExposableWebEndpoint> endpoints = getHandlerMapping().getEndpoints();
|
||||||
.getEndpoints();
|
ExposableWebEndpoint endpoint = endpoints.iterator().next();
|
||||||
EndpointInfo<WebOperation> endpointInfo = endpoints.iterator().next();
|
WebOperation webOperation = endpoint.getOperations().iterator().next();
|
||||||
WebOperation webOperation = endpointInfo.getOperations().iterator().next();
|
Object invoker = ReflectionTestUtils.getField(webOperation, "invoker");
|
||||||
ReflectiveOperationInvoker invoker = (ReflectiveOperationInvoker) webOperation
|
|
||||||
.getInvoker();
|
|
||||||
assertThat(ReflectionTestUtils.getField(invoker, "target"))
|
assertThat(ReflectionTestUtils.getField(invoker, "target"))
|
||||||
.isInstanceOf(CloudFoundryReactiveHealthEndpointWebExtension.class);
|
.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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
import static org.mockito.BDDMockito.given;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link ReactiveCloudFoundrySecurityInterceptor}.
|
* Tests for {@link CloudFoundrySecurityInterceptor}.
|
||||||
*
|
*
|
||||||
* @author Madhura Bhave
|
* @author Madhura Bhave
|
||||||
*/
|
*/
|
||||||
|
@ -49,12 +49,12 @@ public class ReactiveCloudFoundrySecurityInterceptorTests {
|
||||||
@Mock
|
@Mock
|
||||||
private ReactiveCloudFoundrySecurityService securityService;
|
private ReactiveCloudFoundrySecurityService securityService;
|
||||||
|
|
||||||
private ReactiveCloudFoundrySecurityInterceptor interceptor;
|
private CloudFoundrySecurityInterceptor interceptor;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() {
|
public void setup() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
this.interceptor = new ReactiveCloudFoundrySecurityInterceptor(
|
this.interceptor = new CloudFoundrySecurityInterceptor(
|
||||||
this.tokenValidator, this.securityService, "my-app-id");
|
this.tokenValidator, this.securityService, "my-app-id");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ public class ReactiveCloudFoundrySecurityInterceptorTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void preHandleWhenApplicationIdIsNullShouldReturnError() {
|
public void preHandleWhenApplicationIdIsNullShouldReturnError() {
|
||||||
this.interceptor = new ReactiveCloudFoundrySecurityInterceptor(
|
this.interceptor = new CloudFoundrySecurityInterceptor(
|
||||||
this.tokenValidator, this.securityService, null);
|
this.tokenValidator, this.securityService, null);
|
||||||
MockServerWebExchange request = MockServerWebExchange
|
MockServerWebExchange request = MockServerWebExchange
|
||||||
.from(MockServerHttpRequest.get("/a")
|
.from(MockServerHttpRequest.get("/a")
|
||||||
|
@ -105,7 +105,7 @@ public class ReactiveCloudFoundrySecurityInterceptorTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void preHandleWhenCloudFoundrySecurityServiceIsNullShouldReturnError() {
|
public void preHandleWhenCloudFoundrySecurityServiceIsNullShouldReturnError() {
|
||||||
this.interceptor = new ReactiveCloudFoundrySecurityInterceptor(
|
this.interceptor = new CloudFoundrySecurityInterceptor(
|
||||||
this.tokenValidator, null, "my-app-id");
|
this.tokenValidator, null, "my-app-id");
|
||||||
MockServerWebExchange request = MockServerWebExchange.from(MockServerHttpRequest
|
MockServerWebExchange request = MockServerWebExchange.from(MockServerHttpRequest
|
||||||
.get("/a").header(HttpHeaders.AUTHORIZATION, mockAccessToken()).build());
|
.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.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
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.health.HealthEndpointAutoConfiguration;
|
||||||
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
|
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
|
||||||
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
|
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.Endpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||||
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
|
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.actuate.endpoint.web.WebOperation;
|
||||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
|
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.DispatcherServletAutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
|
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
|
||||||
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
|
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.boot.test.util.TestPropertyValues;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
@ -99,8 +98,9 @@ public class CloudFoundryActuatorAutoConfigurationTests {
|
||||||
@Test
|
@Test
|
||||||
public void cloudFoundryPlatformActive() {
|
public void cloudFoundryPlatformActive() {
|
||||||
CloudFoundryWebEndpointServletHandlerMapping handlerMapping = getHandlerMapping();
|
CloudFoundryWebEndpointServletHandlerMapping handlerMapping = getHandlerMapping();
|
||||||
assertThat(handlerMapping.getEndpointMapping().getPath())
|
EndpointMapping endpointMapping = (EndpointMapping) ReflectionTestUtils
|
||||||
.isEqualTo("/cloudfoundryapplication");
|
.getField(handlerMapping, "endpointMapping");
|
||||||
|
assertThat(endpointMapping.getPath()).isEqualTo("/cloudfoundryapplication");
|
||||||
CorsConfiguration corsConfiguration = (CorsConfiguration) ReflectionTestUtils
|
CorsConfiguration corsConfiguration = (CorsConfiguration) ReflectionTestUtils
|
||||||
.getField(handlerMapping, "corsConfiguration");
|
.getField(handlerMapping, "corsConfiguration");
|
||||||
assertThat(corsConfiguration.getAllowedOrigins()).contains("*");
|
assertThat(corsConfiguration.getAllowedOrigins()).contains("*");
|
||||||
|
@ -217,8 +217,7 @@ public class CloudFoundryActuatorAutoConfigurationTests {
|
||||||
this.context.register(TestConfiguration.class);
|
this.context.register(TestConfiguration.class);
|
||||||
this.context.refresh();
|
this.context.refresh();
|
||||||
CloudFoundryWebEndpointServletHandlerMapping handlerMapping = getHandlerMapping();
|
CloudFoundryWebEndpointServletHandlerMapping handlerMapping = getHandlerMapping();
|
||||||
List<EndpointInfo<WebOperation>> endpoints = (List<EndpointInfo<WebOperation>>) handlerMapping
|
Collection<ExposableWebEndpoint> endpoints = handlerMapping.getEndpoints();
|
||||||
.getEndpoints();
|
|
||||||
assertThat(endpoints.stream()
|
assertThat(endpoints.stream()
|
||||||
.filter((candidate) -> "test".equals(candidate.getId())).findFirst())
|
.filter((candidate) -> "test".equals(candidate.getId())).findFirst())
|
||||||
.isNotEmpty();
|
.isNotEmpty();
|
||||||
|
@ -231,9 +230,8 @@ public class CloudFoundryActuatorAutoConfigurationTests {
|
||||||
this.context.register(TestConfiguration.class);
|
this.context.register(TestConfiguration.class);
|
||||||
this.context.refresh();
|
this.context.refresh();
|
||||||
CloudFoundryWebEndpointServletHandlerMapping handlerMapping = getHandlerMapping();
|
CloudFoundryWebEndpointServletHandlerMapping handlerMapping = getHandlerMapping();
|
||||||
List<EndpointInfo<WebOperation>> endpoints = (List<EndpointInfo<WebOperation>>) handlerMapping
|
Collection<ExposableWebEndpoint> endpoints = handlerMapping.getEndpoints();
|
||||||
.getEndpoints();
|
ExposableWebEndpoint endpoint = endpoints.stream()
|
||||||
EndpointInfo<WebOperation> endpoint = endpoints.stream()
|
|
||||||
.filter((candidate) -> "test".equals(candidate.getId())).findFirst()
|
.filter((candidate) -> "test".equals(candidate.getId())).findFirst()
|
||||||
.get();
|
.get();
|
||||||
Collection<WebOperation> operations = endpoint.getOperations();
|
Collection<WebOperation> operations = endpoint.getOperations();
|
||||||
|
@ -249,14 +247,13 @@ public class CloudFoundryActuatorAutoConfigurationTests {
|
||||||
"vcap.application.cf_api:http://my-cloud-controller.com")
|
"vcap.application.cf_api:http://my-cloud-controller.com")
|
||||||
.applyTo(this.context);
|
.applyTo(this.context);
|
||||||
this.context.refresh();
|
this.context.refresh();
|
||||||
Collection<EndpointInfo<WebOperation>> endpoints = this.context
|
Collection<ExposableWebEndpoint> endpoints = this.context
|
||||||
.getBean("cloudFoundryWebEndpointServletHandlerMapping",
|
.getBean("cloudFoundryWebEndpointServletHandlerMapping",
|
||||||
CloudFoundryWebEndpointServletHandlerMapping.class)
|
CloudFoundryWebEndpointServletHandlerMapping.class)
|
||||||
.getEndpoints();
|
.getEndpoints();
|
||||||
EndpointInfo<WebOperation> endpointInfo = endpoints.iterator().next();
|
ExposableWebEndpoint endpoint = endpoints.iterator().next();
|
||||||
WebOperation webOperation = endpointInfo.getOperations().iterator().next();
|
WebOperation webOperation = endpoint.getOperations().iterator().next();
|
||||||
ReflectiveOperationInvoker invoker = (ReflectiveOperationInvoker) webOperation
|
Object invoker = ReflectionTestUtils.getField(webOperation, "invoker");
|
||||||
.getInvoker();
|
|
||||||
assertThat(ReflectionTestUtils.getField(invoker, "target"))
|
assertThat(ReflectionTestUtils.getField(invoker, "target"))
|
||||||
.isInstanceOf(CloudFoundryHealthEndpointWebExtension.class);
|
.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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.AccessLevel;
|
||||||
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException;
|
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException;
|
||||||
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException.Reason;
|
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.Endpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
|
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
|
||||||
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper;
|
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
||||||
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
|
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
|
||||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||||
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
|
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
|
||||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
|
||||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
|
|
||||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||||
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
|
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
|
||||||
|
@ -204,7 +202,7 @@ public class CloudFoundryMvcWebEndpointIntegrationTests {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandlerMapping(
|
public CloudFoundryWebEndpointServletHandlerMapping cloudFoundryWebEndpointServletHandlerMapping(
|
||||||
EndpointDiscoverer<WebOperation> webEndpointDiscoverer,
|
WebEndpointDiscoverer webEndpointDiscoverer,
|
||||||
EndpointMediaTypes endpointMediaTypes,
|
EndpointMediaTypes endpointMediaTypes,
|
||||||
CloudFoundrySecurityInterceptor interceptor) {
|
CloudFoundrySecurityInterceptor interceptor) {
|
||||||
CorsConfiguration corsConfiguration = new CorsConfiguration();
|
CorsConfiguration corsConfiguration = new CorsConfiguration();
|
||||||
|
@ -212,19 +210,19 @@ public class CloudFoundryMvcWebEndpointIntegrationTests {
|
||||||
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST"));
|
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST"));
|
||||||
return new CloudFoundryWebEndpointServletHandlerMapping(
|
return new CloudFoundryWebEndpointServletHandlerMapping(
|
||||||
new EndpointMapping("/cfApplication"),
|
new EndpointMapping("/cfApplication"),
|
||||||
webEndpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
|
webEndpointDiscoverer.getEndpoints(), endpointMediaTypes,
|
||||||
corsConfiguration, interceptor);
|
corsConfiguration, interceptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public WebAnnotationEndpointDiscoverer webEndpointDiscoverer(
|
public WebEndpointDiscoverer webEndpointDiscoverer(
|
||||||
ApplicationContext applicationContext,
|
ApplicationContext applicationContext,
|
||||||
EndpointMediaTypes endpointMediaTypes) {
|
EndpointMediaTypes endpointMediaTypes) {
|
||||||
ParameterMapper parameterMapper = new ConversionServiceParameterMapper(
|
ParameterValueMapper parameterMapper = new ConversionServiceParameterValueMapper(
|
||||||
DefaultConversionService.getSharedInstance());
|
DefaultConversionService.getSharedInstance());
|
||||||
return new WebAnnotationEndpointDiscoverer(applicationContext,
|
return new WebEndpointDiscoverer(applicationContext, parameterMapper,
|
||||||
parameterMapper, endpointMediaTypes,
|
endpointMediaTypes, EndpointPathResolver.useEndpointId(),
|
||||||
EndpointPathResolver.useEndpointId(), null, null);
|
Collections.emptyList(), Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|
|
@ -16,22 +16,20 @@
|
||||||
|
|
||||||
package org.springframework.boot.actuate.autoconfigure.endpoint;
|
package org.springframework.boot.actuate.autoconfigure.endpoint;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.ExpectedException;
|
import org.junit.rules.ExpectedException;
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
|
|
||||||
import org.springframework.boot.actuate.endpoint.EndpointFilter;
|
import org.springframework.boot.actuate.endpoint.EndpointFilter;
|
||||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.Operation;
|
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||||
import org.springframework.mock.env.MockEnvironment;
|
import org.springframework.mock.env.MockEnvironment;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link ExposeExcludePropertyEndpointFilter}.
|
* Tests for {@link ExposeExcludePropertyEndpointFilter}.
|
||||||
|
@ -43,12 +41,7 @@ public class ExposeExcludePropertyEndpointFilterTests {
|
||||||
@Rule
|
@Rule
|
||||||
public ExpectedException thrown = ExpectedException.none();
|
public ExpectedException thrown = ExpectedException.none();
|
||||||
|
|
||||||
private MockEnvironment environment = new MockEnvironment();
|
private ExposeExcludePropertyEndpointFilter<?> filter;
|
||||||
|
|
||||||
private EndpointFilter<Operation> filter;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private TestEndpointDiscoverer discoverer;
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() {
|
public void setup() {
|
||||||
|
@ -56,34 +49,33 @@ public class ExposeExcludePropertyEndpointFilterTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createWhenDiscovererTypeIsNullShouldThrowException() {
|
public void createWhenEndpointTypeIsNullShouldThrowException() {
|
||||||
this.thrown.expect(IllegalArgumentException.class);
|
this.thrown.expect(IllegalArgumentException.class);
|
||||||
this.thrown.expectMessage("Discoverer Type must not be null");
|
this.thrown.expectMessage("EndpointType must not be null");
|
||||||
new ExposeExcludePropertyEndpointFilter<>(null, this.environment, "foo");
|
new ExposeExcludePropertyEndpointFilter<>(null, new MockEnvironment(), "foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createWhenEnvironmentIsNullShouldThrowException() {
|
public void createWhenEnvironmentIsNullShouldThrowException() {
|
||||||
this.thrown.expect(IllegalArgumentException.class);
|
this.thrown.expect(IllegalArgumentException.class);
|
||||||
this.thrown.expectMessage("Environment must not be null");
|
this.thrown.expectMessage("Environment must not be null");
|
||||||
new ExposeExcludePropertyEndpointFilter<>(TestEndpointDiscoverer.class, null,
|
new ExposeExcludePropertyEndpointFilter<>(ExposableEndpoint.class, null, "foo");
|
||||||
"foo");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createWhenPrefixIsNullShouldThrowException() {
|
public void createWhenPrefixIsNullShouldThrowException() {
|
||||||
this.thrown.expect(IllegalArgumentException.class);
|
this.thrown.expect(IllegalArgumentException.class);
|
||||||
this.thrown.expectMessage("Prefix must not be empty");
|
this.thrown.expectMessage("Prefix must not be empty");
|
||||||
new ExposeExcludePropertyEndpointFilter<>(TestEndpointDiscoverer.class,
|
new ExposeExcludePropertyEndpointFilter<>(ExposableEndpoint.class,
|
||||||
this.environment, null);
|
new MockEnvironment(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createWhenPrefixIsEmptyShouldThrowException() {
|
public void createWhenPrefixIsEmptyShouldThrowException() {
|
||||||
this.thrown.expect(IllegalArgumentException.class);
|
this.thrown.expect(IllegalArgumentException.class);
|
||||||
this.thrown.expectMessage("Prefix must not be empty");
|
this.thrown.expectMessage("Prefix must not be empty");
|
||||||
new ExposeExcludePropertyEndpointFilter<>(TestEndpointDiscoverer.class,
|
new ExposeExcludePropertyEndpointFilter<>(ExposableEndpoint.class,
|
||||||
this.environment, "");
|
new MockEnvironment(), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -130,10 +122,11 @@ public class ExposeExcludePropertyEndpointFilterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void matchWhenDiscovererDoesNotMatchShouldMatch() {
|
public void matchWhenDiscovererDoesNotMatchShouldMatch() {
|
||||||
this.environment.setProperty("foo.expose", "bar");
|
MockEnvironment environment = new MockEnvironment();
|
||||||
this.environment.setProperty("foo.exclude", "");
|
environment.setProperty("foo.expose", "bar");
|
||||||
|
environment.setProperty("foo.exclude", "");
|
||||||
this.filter = new ExposeExcludePropertyEndpointFilter<>(
|
this.filter = new ExposeExcludePropertyEndpointFilter<>(
|
||||||
DifferentTestEndpointDiscoverer.class, this.environment, "foo");
|
DifferentTestExposableWebEndpoint.class, environment, "foo");
|
||||||
assertThat(match("baz")).isTrue();
|
assertThat(match("baz")).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,25 +147,27 @@ public class ExposeExcludePropertyEndpointFilterTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupFilter(String expose, String exclude) {
|
private void setupFilter(String expose, String exclude) {
|
||||||
this.environment.setProperty("foo.expose", expose);
|
MockEnvironment environment = new MockEnvironment();
|
||||||
this.environment.setProperty("foo.exclude", exclude);
|
environment.setProperty("foo.expose", expose);
|
||||||
|
environment.setProperty("foo.exclude", exclude);
|
||||||
this.filter = new ExposeExcludePropertyEndpointFilter<>(
|
this.filter = new ExposeExcludePropertyEndpointFilter<>(
|
||||||
TestEndpointDiscoverer.class, this.environment, "foo", "def");
|
TestExposableWebEndpoint.class, environment, "foo", "def");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
private boolean match(String id) {
|
private boolean match(String id) {
|
||||||
EndpointInfo<Operation> info = new EndpointInfo<>(id, true,
|
ExposableEndpoint<?> endpoint = mock(TestExposableWebEndpoint.class);
|
||||||
Collections.emptyList());
|
given(endpoint.getId()).willReturn(id);
|
||||||
return this.filter.match(info, this.discoverer);
|
return ((EndpointFilter) this.filter).match(endpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
private abstract static class TestEndpointDiscoverer
|
private abstract static class TestExposableWebEndpoint
|
||||||
implements EndpointDiscoverer<Operation> {
|
implements ExposableWebEndpoint {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private abstract static class DifferentTestEndpointDiscoverer
|
private abstract static class DifferentTestExposableWebEndpoint
|
||||||
implements EndpointDiscoverer<Operation> {
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.junit.Test;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
|
|
||||||
import org.springframework.boot.actuate.endpoint.EndpointFilter;
|
import org.springframework.boot.actuate.endpoint.EndpointFilter;
|
||||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.Operation;
|
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension;
|
import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension;
|
||||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
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
|
@Override
|
||||||
public boolean match(EndpointInfo<Operation> info,
|
public boolean match(ExposableEndpoint<?> endpoint) {
|
||||||
EndpointDiscoverer<Operation> discoverer) {
|
|
||||||
return true;
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.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.mock.env.MockEnvironment;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ public class DefaultEndpointObjectNameFactoryTests {
|
||||||
@Test
|
@Test
|
||||||
public void generateObjectNameWithUniqueNames() {
|
public void generateObjectNameWithUniqueNames() {
|
||||||
this.properties.setUniqueNames(true);
|
this.properties.setUniqueNames(true);
|
||||||
EndpointMBean endpoint = endpoint("test");
|
ExposableJmxEndpoint endpoint = endpoint("test");
|
||||||
String id = ObjectUtils.getIdentityHexString(endpoint);
|
String id = ObjectUtils.getIdentityHexString(endpoint);
|
||||||
ObjectName objectName = generateObjectName(endpoint);
|
ObjectName objectName = generateObjectName(endpoint);
|
||||||
assertThat(objectName.toString()).isEqualTo(
|
assertThat(objectName.toString()).isEqualTo(
|
||||||
|
@ -105,20 +105,20 @@ public class DefaultEndpointObjectNameFactoryTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ObjectName generateObjectName(EndpointMBean endpointMBean) {
|
private ObjectName generateObjectName(ExposableJmxEndpoint endpoint) {
|
||||||
try {
|
try {
|
||||||
return new DefaultEndpointObjectNameFactory(this.properties, this.mBeanServer,
|
return new DefaultEndpointObjectNameFactory(this.properties, this.mBeanServer,
|
||||||
this.contextId).generate(endpointMBean);
|
this.contextId).getObjectName(endpoint);
|
||||||
}
|
}
|
||||||
catch (MalformedObjectNameException ex) {
|
catch (MalformedObjectNameException ex) {
|
||||||
throw new AssertionError("Invalid object name", ex);
|
throw new AssertionError("Invalid object name", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private EndpointMBean endpoint(String id) {
|
private ExposableJmxEndpoint endpoint(String id) {
|
||||||
EndpointMBean endpointMBean = mock(EndpointMBean.class);
|
ExposableJmxEndpoint endpoint = mock(ExposableJmxEndpoint.class);
|
||||||
given(endpointMBean.getEndpointId()).willReturn(id);
|
given(endpoint.getId()).willReturn(id);
|
||||||
return endpointMBean;
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
|
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
|
||||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.BDDMockito.given;
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link DefaultEndpointPathProvider}.
|
* Tests for {@link DefaultEndpointPathProvider}.
|
||||||
|
@ -39,9 +36,6 @@ import static org.mockito.BDDMockito.given;
|
||||||
*/
|
*/
|
||||||
public class DefaultEndpointPathProviderTests {
|
public class DefaultEndpointPathProviderTests {
|
||||||
|
|
||||||
@Mock
|
|
||||||
private EndpointDiscoverer<WebOperation> discoverer;
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() {
|
public void setup() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
|
@ -78,13 +72,18 @@ public class DefaultEndpointPathProviderTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
private DefaultEndpointPathProvider createProvider(String contextPath) {
|
private DefaultEndpointPathProvider createProvider(String contextPath) {
|
||||||
Collection<EndpointInfo<WebOperation>> endpoints = new ArrayList<>();
|
Collection<ExposableWebEndpoint> endpoints = new ArrayList<>();
|
||||||
endpoints.add(new EndpointInfo<>("foo", true, Collections.emptyList()));
|
endpoints.add(mockEndpoint("foo"));
|
||||||
endpoints.add(new EndpointInfo<>("bar", true, Collections.emptyList()));
|
endpoints.add(mockEndpoint("bar"));
|
||||||
given(this.discoverer.discoverEndpoints()).willReturn(endpoints);
|
WebEndpointProperties properties = new WebEndpointProperties();
|
||||||
WebEndpointProperties webEndpointProperties = new WebEndpointProperties();
|
properties.setBasePath(contextPath);
|
||||||
webEndpointProperties.setBasePath(contextPath);
|
return new DefaultEndpointPathProvider(properties, endpoints);
|
||||||
return new DefaultEndpointPathProvider(this.discoverer, webEndpointProperties);
|
}
|
||||||
|
|
||||||
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,21 +17,20 @@
|
||||||
package org.springframework.boot.actuate.endpoint;
|
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
|
* @author Phillip Webb
|
||||||
* @param <T> the type of the endpoint's operations
|
* @param <E> the endpoint type
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface EndpointFilter<T extends Operation> {
|
public interface EndpointFilter<E extends ExposableEndpoint<?>> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return {@code true} if the filter matches.
|
* Return {@code true} if the filter matches.
|
||||||
* @param info the endpoint info
|
* @param endpoint the endpoint to check
|
||||||
* @param discoverer the endpoint discoverer
|
|
||||||
* @return {@code true} if the filter matches
|
* @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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
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 Andy Wilkinson
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
|
* @author Phillip Webb
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
@FunctionalInterface
|
public interface EndpointsSupplier<E extends ExposableEndpoint<?>> {
|
||||||
public interface EndpointDiscoverer<T extends Operation> {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform endpoint discovery.
|
* Return the provided endpoints.
|
||||||
* @return the discovered 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,58 +16,28 @@
|
||||||
|
|
||||||
package org.springframework.boot.actuate.endpoint;
|
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 Andy Wilkinson
|
||||||
|
* @author Phillip Webb
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public class Operation {
|
public interface 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the {@link OperationType type} of the operation.
|
* Returns the {@link OperationType type} of the operation.
|
||||||
* @return the type
|
* @return the type
|
||||||
*/
|
*/
|
||||||
public OperationType getType() {
|
OperationType getType();
|
||||||
return this.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the {@code OperationInvoker} that can be used to invoke this endpoint
|
* Invoke the underlying operation using the given {@code arguments}.
|
||||||
* operation.
|
* @param arguments the arguments to pass to the operation
|
||||||
* @return the operation invoker
|
* @return the result of the operation, may be {@code null}
|
||||||
*/
|
*/
|
||||||
public OperationInvoker getInvoker() {
|
Object invoke(Map<String, Object> arguments);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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
|
* @author Andy Wilkinson
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
|
* @see Operation
|
||||||
*/
|
*/
|
||||||
public enum OperationType {
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.annotation.Annotation;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
@ -26,12 +25,12 @@ import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.Operation;
|
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.OperationType;
|
||||||
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInfo;
|
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
||||||
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInvokerAdvisor;
|
import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor;
|
||||||
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
|
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
||||||
import org.springframework.boot.actuate.endpoint.reflect.ReflectiveOperationInvoker;
|
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;
|
||||||
import org.springframework.core.MethodIntrospector.MetadataLookup;
|
import org.springframework.core.MethodIntrospector.MetadataLookup;
|
||||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
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
|
* 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 Andy Wilkinson
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
*/
|
*/
|
||||||
class OperationsFactory<T extends Operation> {
|
abstract class DiscoveredOperationsFactory<O extends Operation> {
|
||||||
|
|
||||||
private static final Map<OperationType, Class<? extends Annotation>> OPERATION_TYPES;
|
private static final Map<OperationType, Class<? extends Annotation>> OPERATION_TYPES;
|
||||||
|
|
||||||
|
@ -58,56 +57,56 @@ class OperationsFactory<T extends Operation> {
|
||||||
OPERATION_TYPES = Collections.unmodifiableMap(operationTypes);
|
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;
|
DiscoveredOperationsFactory(ParameterValueMapper parameterValueMapper,
|
||||||
|
Collection<OperationInvokerAdvisor> invokerAdvisors) {
|
||||||
OperationsFactory(OperationFactory<T> operationFactory,
|
this.parameterValueMapper = parameterValueMapper;
|
||||||
ParameterMapper parameterMapper,
|
this.invokerAdvisors = invokerAdvisors;
|
||||||
Collection<? extends OperationMethodInvokerAdvisor> invokerAdvisors) {
|
|
||||||
this.operationFactory = operationFactory;
|
|
||||||
this.parameterMapper = parameterMapper;
|
|
||||||
this.invokerAdvisors = (invokerAdvisors == null ? Collections.emptyList()
|
|
||||||
: new ArrayList<>(invokerAdvisors));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<Method, T> createOperations(String id, Object target, Class<?> type) {
|
public Collection<O> createOperations(String id, Object target) {
|
||||||
return MethodIntrospector.selectMethods(type,
|
return MethodIntrospector.selectMethods(target.getClass(),
|
||||||
(MetadataLookup<T>) (method) -> createOperation(id, target, method));
|
(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()
|
return OPERATION_TYPES.entrySet().stream()
|
||||||
.map((entry) -> createOperation(endpointId, target, method,
|
.map((entry) -> createOperation(endpointId, target, method,
|
||||||
entry.getKey(), entry.getValue()))
|
entry.getKey(), entry.getValue()))
|
||||||
.filter(Objects::nonNull).findFirst().orElse(null);
|
.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) {
|
OperationType operationType, Class<? extends Annotation> annotationType) {
|
||||||
AnnotationAttributes annotationAttributes = AnnotatedElementUtils
|
AnnotationAttributes annotationAttributes = AnnotatedElementUtils
|
||||||
.getMergedAnnotationAttributes(method, annotationType);
|
.getMergedAnnotationAttributes(method, annotationType);
|
||||||
if (annotationAttributes == null) {
|
if (annotationAttributes == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
OperationMethodInfo methodInfo = new OperationMethodInfo(method, operationType,
|
DiscoveredOperationMethod operationMethod = new DiscoveredOperationMethod(method,
|
||||||
annotationAttributes);
|
operationType, annotationAttributes);
|
||||||
OperationInvoker invoker = new ReflectiveOperationInvoker(target, methodInfo,
|
OperationInvoker invoker = new ReflectiveOperationInvoker(target, operationMethod,
|
||||||
this.parameterMapper);
|
this.parameterValueMapper);
|
||||||
return this.operationFactory.createOperation(endpointId, methodInfo, target,
|
invoker = applyAdvisors(endpointId, operationMethod, invoker);
|
||||||
applyAdvisors(endpointId, methodInfo, invoker));
|
return createOperation(endpointId, operationMethod, invoker);
|
||||||
}
|
}
|
||||||
|
|
||||||
private OperationInvoker applyAdvisors(String endpointId,
|
private OperationInvoker applyAdvisors(String endpointId,
|
||||||
OperationMethodInfo methodInfo, OperationInvoker invoker) {
|
OperationMethod operationMethod, OperationInvoker invoker) {
|
||||||
if (this.invokerAdvisors != null) {
|
if (this.invokerAdvisors != null) {
|
||||||
for (OperationMethodInvokerAdvisor advisor : this.invokerAdvisors) {
|
for (OperationInvokerAdvisor advisor : this.invokerAdvisors) {
|
||||||
invoker = advisor.apply(endpointId, methodInfo, invoker);
|
invoker = advisor.apply(endpointId, operationMethod.getOperationType(),
|
||||||
|
operationMethod.getParameters(), invoker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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
|
* @since 2.0.0
|
||||||
* @see EndpointExtension
|
* @see EndpointExtension
|
||||||
* @see FilteredEndpoint
|
* @see FilteredEndpoint
|
||||||
* @see AnnotationEndpointDiscoverer
|
* @see EndpointDiscoverer
|
||||||
*/
|
*/
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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>
|
* } </pre>
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
|
* @see DiscovererEndpointFilter
|
||||||
*/
|
*/
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.boot.actuate.endpoint.reflect;
|
package org.springframework.boot.actuate.endpoint.invoke;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -25,24 +25,25 @@ import org.springframework.util.StringUtils;
|
||||||
* parameters.
|
* parameters.
|
||||||
*
|
*
|
||||||
* @author Madhura Bhave
|
* @author Madhura Bhave
|
||||||
|
* @author Phillip Webb
|
||||||
* @since 2.0.0
|
* @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 "
|
super("Failed to invoke operation because the following required "
|
||||||
+ "parameters were missing: "
|
+ "parameters were missing: "
|
||||||
+ StringUtils.collectionToCommaDelimitedString(parameters));
|
+ StringUtils.collectionToCommaDelimitedString(missingParameters));
|
||||||
this.parameters = parameters;
|
this.missingParameters = missingParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the parameters that were missing.
|
* Returns the parameters that were missing.
|
||||||
* @return the parameters
|
* @return the parameters
|
||||||
*/
|
*/
|
||||||
public Set<String> getParameters() {
|
public Set<OperationParameter> getMissingParameters() {
|
||||||
return this.parameters;
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -14,14 +14,15 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.boot.actuate.endpoint;
|
package org.springframework.boot.actuate.endpoint.invoke;
|
||||||
|
|
||||||
import java.util.Map;
|
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 Andy Wilkinson
|
||||||
|
* @author Phillip Webb
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
|
@ -31,7 +32,8 @@ public interface OperationInvoker {
|
||||||
* Invoke the underlying operation using the given {@code arguments}.
|
* Invoke the underlying operation using the given {@code arguments}.
|
||||||
* @param arguments the arguments to pass to the operation
|
* @param arguments the arguments to pass to the operation
|
||||||
* @return the result of the operation, may be {@code null}
|
* @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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -14,28 +14,28 @@
|
||||||
* limitations under the License.
|
* 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
|
* Allows additional functionality to be applied to an {@link OperationInvoker}.
|
||||||
* to invoke a {@link OperationMethodInfo operation method}.
|
|
||||||
*
|
*
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface OperationMethodInvokerAdvisor {
|
public interface OperationInvokerAdvisor {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply additional functionality to the given invoker.
|
* Apply additional functionality to the given invoker.
|
||||||
* @param endpointId the endpoint ID
|
* @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
|
* @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 apply(String endpointId, OperationType operationType,
|
||||||
OperationInvoker invoker);
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -14,50 +14,50 @@
|
||||||
* limitations under the License.
|
* 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
|
* 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
|
* @author Andy Wilkinson
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public class ParameterMappingException extends RuntimeException {
|
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
|
* Creates a new {@code ParameterMappingException} for a failure that occurred when
|
||||||
* trying to map the given {@code input} to the given {@code type}.
|
* trying to map the given {@code input} to the given {@code type}.
|
||||||
*
|
* @param parameter the parameter being mapping
|
||||||
* @param input the input that was being mapped
|
* @param value the value being mapped
|
||||||
* @param type the type that was being mapped to
|
|
||||||
* @param cause the cause of the mapping failure
|
* @param cause the cause of the mapping failure
|
||||||
*/
|
*/
|
||||||
public ParameterMappingException(Object input, Class<?> type, Throwable cause) {
|
public ParameterMappingException(OperationParameter parameter, Object value,
|
||||||
super("Failed to map " + input + " of type " + input.getClass() + " to type "
|
Throwable cause) {
|
||||||
+ type, cause);
|
super("Failed to map " + value + " of type " + value.getClass() + " to "
|
||||||
this.input = input;
|
+ parameter, cause);
|
||||||
this.type = type;
|
this.parameter = parameter;
|
||||||
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the input that was to be mapped.
|
* Return the parameter being mapped.
|
||||||
* @return the input
|
* @return the parameter
|
||||||
*/
|
*/
|
||||||
public Object getInput() {
|
public OperationParameter getParameter() {
|
||||||
return this.input;
|
return this.parameter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the type to be mapped to.
|
* Return the value being mapped.
|
||||||
* @return the type
|
* @return the value
|
||||||
*/
|
*/
|
||||||
public Class<?> getType() {
|
public Object getValue() {
|
||||||
return this.type;
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -14,25 +14,25 @@
|
||||||
* limitations under the License.
|
* 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
|
* @author Stephane Nicoll
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface ParameterMapper {
|
public interface ParameterValueMapper {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map the specified {@code input} parameter to the given {@code parameterType}.
|
* Map the specified {@code input} parameter to the given {@code parameterType}.
|
||||||
|
* @param parameter the parameter to map
|
||||||
* @param value a parameter value
|
* @param value a parameter value
|
||||||
* @param type the required type of the parameter
|
|
||||||
* @return a value suitable for that parameter
|
* @return a value suitable for that parameter
|
||||||
* @param <T> the actual type of the parameter
|
|
||||||
* @throws ParameterMappingException when a mapping failure occurs
|
* @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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -14,49 +14,56 @@
|
||||||
* limitations under the License.
|
* 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.invoke.OperationParameter;
|
||||||
import org.springframework.boot.actuate.endpoint.reflect.ParameterMappingException;
|
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.boot.context.properties.bind.convert.BinderConversionService;
|
||||||
import org.springframework.core.convert.ConversionService;
|
import org.springframework.core.convert.ConversionService;
|
||||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link ParameterMapper} that uses a {@link ConversionService} to map parameter values
|
* {@link ParameterValueMapper} backed by a {@link ConversionService}.
|
||||||
* if necessary.
|
|
||||||
*
|
*
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public class ConversionServiceParameterMapper implements ParameterMapper {
|
public class ConversionServiceParameterValueMapper implements ParameterValueMapper {
|
||||||
|
|
||||||
private final ConversionService conversionService;
|
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
|
* @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);
|
this.conversionService = new BinderConversionService(conversionService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> T mapParameter(Object input, Class<T> parameterType) {
|
public Object mapParameterValue(OperationParameter parameter, Object value)
|
||||||
|
throws ParameterMappingException {
|
||||||
try {
|
try {
|
||||||
return this.conversionService.convert(input, parameterType);
|
return this.conversionService.convert(value, parameter.getType());
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
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();
|
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
|
||||||
IsoOffsetDateTimeConverter.registerConverter(conversionService);
|
IsoOffsetDateTimeConverter.registerConverter(conversionService);
|
||||||
return conversionService;
|
return conversionService;
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* 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.OffsetDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,4 +17,4 @@
|
||||||
/**
|
/**
|
||||||
* Converter support for actuator endpoints.
|
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -14,17 +14,14 @@
|
||||||
* limitations under the License.
|
* 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.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.OperationType;
|
||||||
|
import org.springframework.boot.actuate.endpoint.invoke.OperationParameters;
|
||||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||||
import org.springframework.core.ParameterNameDiscoverer;
|
import org.springframework.core.ParameterNameDiscoverer;
|
||||||
import org.springframework.core.annotation.AnnotationAttributes;
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,7 +31,7 @@ import org.springframework.util.Assert;
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
* @see ReflectiveOperationInvoker
|
* @see ReflectiveOperationInvoker
|
||||||
*/
|
*/
|
||||||
public final class OperationMethodInfo {
|
public class OperationMethod {
|
||||||
|
|
||||||
private static final ParameterNameDiscoverer DEFAULT_PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
|
private static final ParameterNameDiscoverer DEFAULT_PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
|
||||||
|
|
||||||
|
@ -42,18 +39,20 @@ public final class OperationMethodInfo {
|
||||||
|
|
||||||
private final OperationType operationType;
|
private final OperationType operationType;
|
||||||
|
|
||||||
private final AnnotationAttributes annotationAttributes;
|
private final OperationParameters operationParameters;
|
||||||
|
|
||||||
private final ParameterNameDiscoverer parameterNameDiscoverer = DEFAULT_PARAMETER_NAME_DISCOVERER;
|
/**
|
||||||
|
* Create a new {@link OperationMethod} instance.
|
||||||
public OperationMethodInfo(Method method, OperationType operationType,
|
* @param method the source method
|
||||||
AnnotationAttributes annotationAttributes) {
|
* @param operationType the operation type
|
||||||
|
*/
|
||||||
|
public OperationMethod(Method method, OperationType operationType) {
|
||||||
Assert.notNull(method, "Method must not be null");
|
Assert.notNull(method, "Method must not be null");
|
||||||
Assert.notNull(operationType, "Operation Type must not be null");
|
Assert.notNull(operationType, "OperationType must not be null");
|
||||||
Assert.notNull(annotationAttributes, "Annotation Attributes must not be null");
|
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.operationType = operationType;
|
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 operation parameters.
|
||||||
* @return the produced mime type
|
* @return the operation parameters
|
||||||
*/
|
*/
|
||||||
public String[] getProduces() {
|
public OperationParameters getParameters() {
|
||||||
return this.annotationAttributes.getStringArray("produces");
|
return this.operationParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Return a map of method parameters with the key being the discovered parameter name.
|
public String toString() {
|
||||||
* @return the method parameters
|
return "Operation " + this.operationType.name().toLowerCase() + " method "
|
||||||
*/
|
+ this.method;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,4 +17,4 @@
|
||||||
/**
|
/**
|
||||||
* Endpoint reflection support.
|
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -14,11 +14,11 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.boot.actuate.endpoint.cache;
|
package org.springframework.boot.actuate.endpoint.invoker.cache;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.OperationInvoker;
|
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,7 +30,7 @@ import org.springframework.util.Assert;
|
||||||
*/
|
*/
|
||||||
public class CachingOperationInvoker implements OperationInvoker {
|
public class CachingOperationInvoker implements OperationInvoker {
|
||||||
|
|
||||||
private final OperationInvoker target;
|
private final OperationInvoker invoker;
|
||||||
|
|
||||||
private final long timeToLive;
|
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
|
* Create a new instance with the target {@link OperationInvoker} to use to compute
|
||||||
* the response and the time to live for the cache.
|
* 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
|
* @param timeToLive the maximum time in milliseconds that a response can be cached
|
||||||
*/
|
*/
|
||||||
public CachingOperationInvoker(OperationInvoker target, long timeToLive) {
|
CachingOperationInvoker(OperationInvoker invoker, long timeToLive) {
|
||||||
Assert.state(timeToLive > 0, "TimeToLive must be strictly positive");
|
Assert.isTrue(timeToLive > 0, "TimeToLive must be strictly positive");
|
||||||
this.target = target;
|
this.invoker = invoker;
|
||||||
this.timeToLive = timeToLive;
|
this.timeToLive = timeToLive;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ public class CachingOperationInvoker implements OperationInvoker {
|
||||||
long accessTime = System.currentTimeMillis();
|
long accessTime = System.currentTimeMillis();
|
||||||
CachedResponse cached = this.cachedResponse;
|
CachedResponse cached = this.cachedResponse;
|
||||||
if (cached == null || cached.isStale(accessTime, this.timeToLive)) {
|
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);
|
this.cachedResponse = new CachedResponse(response, accessTime);
|
||||||
return response;
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -14,23 +14,22 @@
|
||||||
* limitations under the License.
|
* 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 java.util.function.Function;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.OperationInvoker;
|
|
||||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||||
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInfo;
|
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
||||||
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInvokerAdvisor;
|
import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor;
|
||||||
|
import org.springframework.boot.actuate.endpoint.invoke.OperationParameters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link OperationMethodInvokerAdvisor} to optionally wrap an {@link OperationInvoker}
|
* {@link OperationInvokerAdvisor} to optionally provide result caching support.
|
||||||
* with a {@link CachingOperationInvoker}.
|
|
||||||
*
|
*
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public class CachingOperationInvokerAdvisor implements OperationMethodInvokerAdvisor {
|
public class CachingOperationInvokerAdvisor implements OperationInvokerAdvisor {
|
||||||
|
|
||||||
private final Function<String, Long> endpointIdTimeToLive;
|
private final Function<String, Long> endpointIdTimeToLive;
|
||||||
|
|
||||||
|
@ -39,10 +38,9 @@ public class CachingOperationInvokerAdvisor implements OperationMethodInvokerAdv
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OperationInvoker apply(String endpointId, OperationMethodInfo methodInfo,
|
public OperationInvoker apply(String endpointId, OperationType operationType,
|
||||||
OperationInvoker invoker) {
|
OperationParameters parameters, OperationInvoker invoker) {
|
||||||
if (methodInfo.getOperationType() == OperationType.READ
|
if (operationType == OperationType.READ && !parameters.hasParameters()) {
|
||||||
&& methodInfo.getParameters().isEmpty()) {
|
|
||||||
Long timeToLive = this.endpointIdTimeToLive.apply(endpointId);
|
Long timeToLive = this.endpointIdTimeToLive.apply(endpointId);
|
||||||
if (timeToLive != null && timeToLive > 0) {
|
if (timeToLive != null && timeToLive > 0) {
|
||||||
return new CachingOperationInvoker(invoker, timeToLive);
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,4 +17,4 @@
|
||||||
/**
|
/**
|
||||||
* Caching support for actuator endpoints.
|
* 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;
|
package org.springframework.boot.actuate.endpoint.jmx;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import javax.management.Attribute;
|
import javax.management.Attribute;
|
||||||
import javax.management.AttributeList;
|
import javax.management.AttributeList;
|
||||||
|
@ -32,76 +31,87 @@ import javax.management.ReflectionException;
|
||||||
|
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
import org.springframework.boot.actuate.endpoint.invoke.MissingParametersException;
|
||||||
import org.springframework.boot.actuate.endpoint.reflect.ParameterMappingException;
|
import org.springframework.boot.actuate.endpoint.invoke.ParameterMappingException;
|
||||||
import org.springframework.boot.actuate.endpoint.reflect.ParametersMissingException;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ClassUtils;
|
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 Stephane Nicoll
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
|
* @author Phillip Webb
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
* @see EndpointMBeanInfoAssembler
|
|
||||||
*/
|
*/
|
||||||
public class EndpointMBean implements DynamicMBean {
|
public class EndpointMBean implements DynamicMBean {
|
||||||
|
|
||||||
private static final boolean REACTOR_PRESENT = ClassUtils.isPresent(
|
private static final boolean REACTOR_PRESENT = ClassUtils.isPresent(
|
||||||
"reactor.core.publisher.Mono", EndpointMBean.class.getClassLoader());
|
"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,
|
private final MBeanInfo info;
|
||||||
EndpointMBeanInfo endpointInfo) {
|
|
||||||
this.operationResponseConverter = operationResponseConverter;
|
private final Map<String, JmxOperation> operations;
|
||||||
this.endpointInfo = endpointInfo;
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private Map<String, JmxOperation> getOperations(ExposableJmxEndpoint endpoint) {
|
||||||
* Return the id of the related endpoint.
|
Map<String, JmxOperation> operations = new HashMap<>();
|
||||||
* @return the endpoint id
|
endpoint.getOperations()
|
||||||
*/
|
.forEach((operation) -> operations.put(operation.getName(), operation));
|
||||||
public String getEndpointId() {
|
return Collections.unmodifiableMap(operations);
|
||||||
return this.endpointInfo.getEndpointId();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MBeanInfo getMBeanInfo() {
|
public MBeanInfo getMBeanInfo() {
|
||||||
return this.endpointInfo.getMbeanInfo();
|
return this.info;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object invoke(String actionName, Object[] params, String[] signature)
|
public Object invoke(String actionName, Object[] params, String[] signature)
|
||||||
throws MBeanException, ReflectionException {
|
throws MBeanException, ReflectionException {
|
||||||
JmxOperation operation = this.endpointInfo.getOperations().get(actionName);
|
JmxOperation operation = this.operations.get(actionName);
|
||||||
if (operation != null) {
|
if (operation == null) {
|
||||||
Map<String, Object> arguments = getArguments(params,
|
String message = "Endpoint with id '" + this.endpoint.getId()
|
||||||
operation.getParameters());
|
+ "' has no operation named " + actionName;
|
||||||
|
throw new ReflectionException(new IllegalArgumentException(message), message);
|
||||||
|
}
|
||||||
|
return invoke(operation, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object invoke(JmxOperation operation, Object[] params) {
|
||||||
try {
|
try {
|
||||||
Object result = operation.getInvoker().invoke(arguments);
|
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) {
|
if (REACTOR_PRESENT) {
|
||||||
result = ReactiveHandler.handle(result);
|
result = ReactiveHandler.handle(result);
|
||||||
}
|
}
|
||||||
return this.operationResponseConverter.apply(result);
|
return this.responseMapper.mapResponse(result);
|
||||||
|
}
|
||||||
|
catch (MissingParametersException | ParameterMappingException ex) {
|
||||||
|
throw new IllegalArgumentException(ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
catch (ParametersMissingException | ParameterMappingException ex) {
|
|
||||||
throw new IllegalArgumentException(ex.getMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
private Map<String, Object> getArguments(String[] parameterNames, Object[] params) {
|
||||||
throw new ReflectionException(new IllegalArgumentException(
|
|
||||||
String.format("Endpoint with id '%s' has no operation named %s",
|
|
||||||
this.endpointInfo.getEndpointId(), actionName)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, Object> getArguments(Object[] params,
|
|
||||||
List<JmxEndpointOperationParameterInfo> parameters) {
|
|
||||||
Map<String, Object> arguments = new HashMap<>();
|
Map<String, Object> arguments = new HashMap<>();
|
||||||
for (int i = 0; i < params.length; i++) {
|
for (int i = 0; i < params.length; i++) {
|
||||||
arguments.put(parameters.get(i).getName(), params[i]);
|
arguments.put(parameterNames[i], params[i]);
|
||||||
}
|
}
|
||||||
return arguments;
|
return arguments;
|
||||||
}
|
}
|
||||||
|
@ -109,13 +119,13 @@ public class EndpointMBean implements DynamicMBean {
|
||||||
@Override
|
@Override
|
||||||
public Object getAttribute(String attribute)
|
public Object getAttribute(String attribute)
|
||||||
throws AttributeNotFoundException, MBeanException, ReflectionException {
|
throws AttributeNotFoundException, MBeanException, ReflectionException {
|
||||||
throw new AttributeNotFoundException();
|
throw new AttributeNotFoundException("EndpointMBeans do not support attributes");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setAttribute(Attribute attribute) throws AttributeNotFoundException,
|
public void setAttribute(Attribute attribute) throws AttributeNotFoundException,
|
||||||
InvalidAttributeValueException, MBeanException, ReflectionException {
|
InvalidAttributeValueException, MBeanException, ReflectionException {
|
||||||
throw new AttributeNotFoundException();
|
throw new AttributeNotFoundException("EndpointMBeans do not support attributes");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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 {
|
public interface EndpointObjectNameFactory {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate an {@link ObjectName} for the specified {@link EndpointMBean endpoint}.
|
* Generate an {@link ObjectName} for the specified {@link ExposableJmxEndpoint
|
||||||
* @param mBean the endpoint to handle
|
* endpoint}.
|
||||||
|
* @param endpoint the endpoint MBean to handle
|
||||||
* @return the {@link ObjectName} to use for the endpoint
|
* @return the {@link ObjectName} to use for the endpoint
|
||||||
* @throws MalformedObjectNameException if the object name is invalid
|
* @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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
package org.springframework.boot.actuate.endpoint.jmx;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.Operation;
|
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.
|
* An operation on a JMX endpoint.
|
||||||
*
|
*
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
|
* @author Phillip Webb
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public class JmxOperation extends Operation {
|
public interface 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the name of the operation.
|
* Returns the name of the operation.
|
||||||
* @return the operation name
|
* @return the operation name
|
||||||
*/
|
*/
|
||||||
public String getOperationName() {
|
String getName();
|
||||||
return this.operationName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the type of the output of the operation.
|
* Returns the type of the output of the operation.
|
||||||
* @return the output type
|
* @return the output type
|
||||||
*/
|
*/
|
||||||
public Class<?> getOutputType() {
|
Class<?> getOutputType();
|
||||||
return this.outputType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the description of the operation.
|
* Returns the description of the operation.
|
||||||
* @return the operation description
|
* @return the operation description
|
||||||
*/
|
*/
|
||||||
public String getDescription() {
|
String getDescription();
|
||||||
return this.description;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the parameters of the operation.
|
* Returns the parameters the operation expects in the order that they should be
|
||||||
* @return the operation parameters
|
* provided.
|
||||||
|
* @return the operation parameter names
|
||||||
*/
|
*/
|
||||||
public List<JmxEndpointOperationParameterInfo> getParameters() {
|
List<JmxOperationParameter> 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.
|
* Describes the parameters of an operation on a JMX endpoint.
|
||||||
*
|
*
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
|
* @author Phillip Webb
|
||||||
* @since 2.0.0
|
* @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;
|
/**
|
||||||
|
* Return the type of the operation parameter.
|
||||||
private final String description;
|
* @return the type
|
||||||
|
*/
|
||||||
public JmxEndpointOperationParameterInfo(String name, Class<?> type,
|
Class<?> getType();
|
||||||
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 description of the parameter or {@code null} if none is available.
|
* Return the description of the parameter or {@code null} if none is available.
|
||||||
* @return the description or {@code null}
|
* @return the description or {@code null}
|
||||||
*/
|
*/
|
||||||
public String getDescription() {
|
String getDescription();
|
||||||
return this.description;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
package org.springframework.boot.actuate.endpoint.jmx;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@code JmxOperationResponseMapper} maps an operation's response to a JMX-friendly
|
* Maps an operation's response to a JMX-friendly form.
|
||||||
* form.
|
|
||||||
*
|
*
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public interface JmxOperationResponseMapper {
|
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.
|
* Map the response type to its JMX compliant counterpart.
|
||||||
* @param responseType the operation's response type
|
* @param responseType the operation's response type
|
||||||
|
@ -39,4 +31,11 @@ public interface JmxOperationResponseMapper {
|
||||||
*/
|
*/
|
||||||
Class<?> mapResponseType(Class<?> responseType);
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
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.Endpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.FilteredEndpoint;
|
import org.springframework.boot.actuate.endpoint.annotation.FilteredEndpoint;
|
||||||
import org.springframework.core.annotation.AliasFor;
|
import org.springframework.core.annotation.AliasFor;
|
||||||
|
@ -33,7 +32,6 @@ import org.springframework.core.annotation.AliasFor;
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
* @see AnnotationEndpointDiscoverer
|
|
||||||
*/
|
*/
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
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.EndpointFilter;
|
||||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
import org.springframework.boot.actuate.endpoint.annotation.DiscovererEndpointFilter;
|
||||||
import org.springframework.boot.actuate.endpoint.jmx.JmxOperation;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link EndpointFilter} for endpoints discovered by
|
* {@link EndpointFilter} for endpoints discovered by {@link JmxEndpointDiscoverer}.
|
||||||
* {@link JmxAnnotationEndpointDiscoverer}.
|
|
||||||
*
|
*
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
*/
|
*/
|
||||||
class JmxEndpointFilter implements EndpointFilter<JmxOperation> {
|
class JmxEndpointFilter extends DiscovererEndpointFilter {
|
||||||
|
|
||||||
@Override
|
JmxEndpointFilter() {
|
||||||
public boolean match(EndpointInfo<JmxOperation> info,
|
super(JmxEndpointDiscoverer.class);
|
||||||
EndpointDiscoverer<JmxOperation> discoverer) {
|
|
||||||
return (discoverer instanceof JmxAnnotationEndpointDiscoverer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A resolver for {@link Link links} to web endpoints.
|
* 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
|
* Resolves links to the operations of the given {code webEndpoints} based on a
|
||||||
* request with the given {@code requestUrl}.
|
* 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
|
* @param requestUrl the url of the request for the endpoint links
|
||||||
* @return the links
|
* @return the links
|
||||||
*/
|
*/
|
||||||
public Map<String, Link> resolveLinks(
|
public Map<String, Link> resolveLinks(Collection<ExposableWebEndpoint> endpoints,
|
||||||
Collection<EndpointInfo<WebOperation>> webEndpoints, String requestUrl) {
|
String requestUrl) {
|
||||||
String normalizedUrl = normalizeRequestUrl(requestUrl);
|
String normalizedUrl = normalizeRequestUrl(requestUrl);
|
||||||
Map<String, Link> links = new LinkedHashMap<>();
|
Map<String, Link> links = new LinkedHashMap<>();
|
||||||
links.put("self", new Link(normalizedUrl));
|
links.put("self", new Link(normalizedUrl));
|
||||||
for (EndpointInfo<WebOperation> endpoint : webEndpoints) {
|
for (ExposableWebEndpoint endpoint : endpoints) {
|
||||||
for (WebOperation operation : endpoint.getOperations()) {
|
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)));
|
.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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.
|
* Returns the media types produced by an endpoint.
|
||||||
*
|
|
||||||
* @return the produced media types
|
* @return the produced media types
|
||||||
*/
|
*/
|
||||||
public List<String> getProduced() {
|
public List<String> getProduced() {
|
||||||
|
@ -58,7 +57,6 @@ public class EndpointMediaTypes {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the media types consumed by an endpoint.
|
* Returns the media types consumed by an endpoint.
|
||||||
*
|
|
||||||
* @return the consumed media types
|
* @return the consumed media types
|
||||||
*/
|
*/
|
||||||
public List<String> getConsumed() {
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
package org.springframework.boot.actuate.endpoint.web;
|
||||||
|
|
||||||
import org.springframework.core.style.ToStringCreator;
|
import org.springframework.core.style.ToStringCreator;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Details for a link in a
|
* Details for a link in a
|
||||||
|
@ -37,6 +38,7 @@ public class Link {
|
||||||
* @param href the href
|
* @param href the href
|
||||||
*/
|
*/
|
||||||
public Link(String href) {
|
public Link(String href) {
|
||||||
|
Assert.notNull(href, "HREF must not be null");
|
||||||
this.href = href;
|
this.href = href;
|
||||||
this.templated = href.contains("{");
|
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