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-11581pull/11626/merge
parent
dc935fba48
commit
1d39feffea
@ -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 {
|
||||
|
||||
}
|
8
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundrySecurityInterceptor.java → spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundrySecurityInterceptor.java
8
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundrySecurityInterceptor.java → spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundrySecurityInterceptor.java
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
69
spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebAnnotationEndpointDiscovererTests.java → spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java
69
spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebAnnotationEndpointDiscovererTests.java → spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java
@ -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,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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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,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);
|
||||
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
37
spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/convert/ConversionServiceParameterMapper.java → spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/convert/ConversionServiceParameterValueMapper.java
37
spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/convert/ConversionServiceParameterMapper.java → spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/convert/ConversionServiceParameterValueMapper.java
@ -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;
|
@ -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,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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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> {
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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,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();
|
||||
}
|
||||
|
||||
}
|
@ -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> {
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue