Overhaul actuator endpoint code

Refactor several areas of the actuator endpoint code in order to make
future extensions easier. The primary goal is to introduce the concept
of an `ExposableEndpoint` that has technology specific subclasses and
can carry additional data for filters to use. Many other changes have
been made along the way including:

* A new EndpointSupplier interface that allows cleaner separation of
  supplying vs discovering endpoints. This allows cleaner class names
  and allows for better auto-configuration since a user can choose to
  provide their own supplier entirely.

* A `DiscoveredEndpoint` interface that allows the `EndpointFilter`
  to be greatly simplified. A filter now doesn't need to know about
  discovery concerns unless absolutely necessary.

* Improved naming and package structure. Many technology specific
  concerns are now grouped in a better way. Related concerns are
  co-located and concepts from one area no longer leakage into another.

* Simplified `HandlerMapping` implementations. Many common concerns have
  been pulled up helping to create simpler subclasses.

* Simplified JMX adapters. Many of the intermediary `Info` classes have
  been removed. The `DiscoveredJmxOperation` is now responsible for
  mapping methods to operations.

* A specific @`HealthEndpointCloudFoundryExtension` for Cloud Foundry.
  The extension logic used to create a "full" health endpoint extension
  has been made explicit.

Fixes gh-11428
Fixes gh-11581
This commit is contained in:
Phillip Webb
2018-01-09 20:57:10 -08:00
parent dc935fba48
commit 1d39feffea
157 changed files with 6913 additions and 5183 deletions

View File

@@ -1,126 +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.Map;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link NamePatternFilter}.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @author Dylian Bego
*/
public class NamePatternFilterTests {
@Test
public void nonRegex() {
MockNamePatternFilter filter = new MockNamePatternFilter();
assertThat(filter.getResults("not.a.regex")).containsEntry("not.a.regex",
"not.a.regex");
assertThat(filter.isGetNamesCalled()).isFalse();
}
@Test
public void nonRegexThatContainsRegexPart() {
MockNamePatternFilter filter = new MockNamePatternFilter();
assertThat(filter.getResults("*")).containsEntry("*", "*");
assertThat(filter.isGetNamesCalled()).isFalse();
}
@Test
public void regexRepetitionZeroOrMore() {
MockNamePatternFilter filter = new MockNamePatternFilter();
Map<String, Object> results = filter.getResults("fo.*");
assertThat(results.get("foo")).isEqualTo("foo");
assertThat(results.get("fool")).isEqualTo("fool");
assertThat(filter.isGetNamesCalled()).isTrue();
}
@Test
public void regexRepetitionOneOrMore() {
MockNamePatternFilter filter = new MockNamePatternFilter();
Map<String, Object> results = filter.getResults("fo.+");
assertThat(results.get("foo")).isEqualTo("foo");
assertThat(results.get("fool")).isEqualTo("fool");
assertThat(filter.isGetNamesCalled()).isTrue();
}
@Test
public void regexEndAnchor() {
MockNamePatternFilter filter = new MockNamePatternFilter();
Map<String, Object> results = filter.getResults("foo$");
assertThat(results.get("foo")).isEqualTo("foo");
assertThat(results.get("fool")).isNull();
assertThat(filter.isGetNamesCalled()).isTrue();
}
@Test
public void regexStartAnchor() {
MockNamePatternFilter filter = new MockNamePatternFilter();
Map<String, Object> results = filter.getResults("^foo");
assertThat(results.get("foo")).isEqualTo("foo");
assertThat(results.get("fool")).isNull();
assertThat(filter.isGetNamesCalled()).isTrue();
}
@Test
public void regexCharacterClass() {
MockNamePatternFilter filter = new MockNamePatternFilter();
Map<String, Object> results = filter.getResults("fo[a-z]l");
assertThat(results.get("foo")).isNull();
assertThat(results.get("fool")).isEqualTo("fool");
assertThat(filter.isGetNamesCalled()).isTrue();
}
private static class MockNamePatternFilter extends NamePatternFilter<Object> {
MockNamePatternFilter() {
super(null);
}
private boolean getNamesCalled;
@Override
protected Object getOptionalValue(Object source, String name) {
return name;
}
@Override
protected Object getValue(Object source, String name) {
return name;
}
@Override
protected void getNames(Object source, NameCallback callback) {
this.getNamesCalled = true;
callback.addName("foo");
callback.addName("fool");
callback.addName("fume");
}
public boolean isGetNamesCalled() {
return this.getNamesCalled;
}
}
}

View File

@@ -1,497 +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 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 java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
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.OperationInvoker;
import org.springframework.boot.actuate.endpoint.cache.CachingOperationInvoker;
import org.springframework.boot.actuate.endpoint.cache.CachingOperationInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper;
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInfo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AliasFor;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link AnnotationEndpointDiscoverer}.
*
* @author Andy Wilkinson
* @author Stephane Nicoll
*/
public class AnnotationEndpointDiscovererTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Test
public void discoverWorksWhenThereAreNoEndpoints() {
load(EmptyConfiguration.class,
(context) -> assertThat(new TestAnnotationEndpointDiscoverer(context)
.discoverEndpoints().isEmpty()));
}
@Test
public void endpointIsDiscovered() {
load(TestEndpointConfiguration.class, hasTestEndpoint());
}
@Test
public void endpointInParentContextIsDiscovered() {
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext(
TestEndpointConfiguration.class);
loadWithParent(parent, EmptyConfiguration.class, hasTestEndpoint());
}
private Consumer<AnnotationConfigApplicationContext> hasTestEndpoint() {
return (context) -> {
Map<String, EndpointInfo<TestEndpointOperation>> endpoints = mapEndpoints(
new TestAnnotationEndpointDiscoverer(context).discoverEndpoints());
assertThat(endpoints).containsOnlyKeys("test");
Map<Method, TestEndpointOperation> operations = mapOperations(
endpoints.get("test"));
assertThat(operations).hasSize(4);
assertThat(operations).containsKeys(
ReflectionUtils.findMethod(TestEndpoint.class, "getAll"),
ReflectionUtils.findMethod(TestEndpoint.class, "getOne",
String.class),
ReflectionUtils.findMethod(TestEndpoint.class, "update", String.class,
String.class),
ReflectionUtils.findMethod(TestEndpoint.class, "deleteOne",
String.class));
};
}
@Test
public void subclassedEndpointIsDiscovered() {
load(TestEndpointSubclassConfiguration.class, (context) -> {
Map<String, EndpointInfo<TestEndpointOperation>> endpoints = mapEndpoints(
new TestAnnotationEndpointDiscoverer(context).discoverEndpoints());
assertThat(endpoints).containsOnlyKeys("test");
Map<Method, TestEndpointOperation> operations = mapOperations(
endpoints.get("test"));
assertThat(operations).hasSize(5);
assertThat(operations).containsKeys(
ReflectionUtils.findMethod(TestEndpoint.class, "getAll"),
ReflectionUtils.findMethod(TestEndpoint.class, "getOne",
String.class),
ReflectionUtils.findMethod(TestEndpoint.class, "update", String.class,
String.class),
ReflectionUtils.findMethod(TestEndpoint.class, "deleteOne",
String.class),
ReflectionUtils.findMethod(TestEndpointSubclass.class,
"updateWithMoreArguments", String.class, String.class,
String.class));
});
}
@Test
public void discoveryFailsWhenTwoEndpointsHaveTheSameId() {
load(ClashingEndpointConfiguration.class, (context) -> {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Found two endpoints with the id 'test': ");
new TestAnnotationEndpointDiscoverer(context).discoverEndpoints();
});
}
@Test
public void endpointMainReadOperationIsNotCachedWithTtlSetToZero() {
Function<String, Long> timeToLive = (endpointId) -> 0L;
load(TestEndpointConfiguration.class, (context) -> {
Map<String, EndpointInfo<TestEndpointOperation>> endpoints = mapEndpoints(
new TestAnnotationEndpointDiscoverer(context, timeToLive)
.discoverEndpoints());
assertThat(endpoints).containsOnlyKeys("test");
Map<Method, TestEndpointOperation> operations = mapOperations(
endpoints.get("test"));
assertThat(operations).hasSize(4);
operations.values().forEach(operation -> assertThat(operation.getInvoker())
.isNotInstanceOf(CachingOperationInvoker.class));
});
}
@Test
public void endpointMainReadOperationIsNotCachedWithNonMatchingId() {
Function<String, Long> timeToLive = (id) -> (id.equals("foo") ? 500L : 0L);
load(TestEndpointConfiguration.class, (context) -> {
Map<String, EndpointInfo<TestEndpointOperation>> endpoints = mapEndpoints(
new TestAnnotationEndpointDiscoverer(context, timeToLive)
.discoverEndpoints());
assertThat(endpoints).containsOnlyKeys("test");
Map<Method, TestEndpointOperation> operations = mapOperations(
endpoints.get("test"));
assertThat(operations).hasSize(4);
operations.values().forEach(operation -> assertThat(operation.getInvoker())
.isNotInstanceOf(CachingOperationInvoker.class));
});
}
@Test
public void endpointMainReadOperationIsCachedWithMatchingId() {
Function<String, Long> timeToLive = (id) -> (id.equals("test") ? 500L : 0L);
load(TestEndpointConfiguration.class, (context) -> {
Map<String, EndpointInfo<TestEndpointOperation>> endpoints = mapEndpoints(
new TestAnnotationEndpointDiscoverer(context, timeToLive)
.discoverEndpoints());
assertThat(endpoints).containsOnlyKeys("test");
Map<Method, TestEndpointOperation> operations = mapOperations(
endpoints.get("test"));
OperationInvoker getAllOperationInvoker = operations
.get(ReflectionUtils.findMethod(TestEndpoint.class, "getAll"))
.getInvoker();
assertThat(getAllOperationInvoker)
.isInstanceOf(CachingOperationInvoker.class);
assertThat(((CachingOperationInvoker) getAllOperationInvoker).getTimeToLive())
.isEqualTo(500);
assertThat(operations.get(ReflectionUtils.findMethod(TestEndpoint.class,
"getOne", String.class)).getInvoker())
.isNotInstanceOf(CachingOperationInvoker.class);
assertThat(operations.get(ReflectionUtils.findMethod(TestEndpoint.class,
"update", String.class, String.class)).getInvoker())
.isNotInstanceOf(CachingOperationInvoker.class);
});
}
@Test
public void specializedEndpointsAreFilteredFromRegular() {
load(TestEndpointsConfiguration.class, (context) -> {
Map<String, EndpointInfo<TestEndpointOperation>> endpoints = mapEndpoints(
new TestAnnotationEndpointDiscoverer(context).discoverEndpoints());
assertThat(endpoints).containsOnlyKeys("test");
});
}
@Test
public void specializedEndpointsAreNotFilteredFromSpecialized() {
load(TestEndpointsConfiguration.class, (context) -> {
Map<String, EndpointInfo<SpecializedTestEndpointOperation>> endpoints = mapEndpoints(
new SpecializedTestAnnotationEndpointDiscoverer(context)
.discoverEndpoints());
assertThat(endpoints).containsOnlyKeys("test", "specialized");
});
}
@Test
public void extensionsAreApplied() {
load(TestEndpointsConfiguration.class, (context) -> {
Map<String, EndpointInfo<SpecializedTestEndpointOperation>> endpoints = mapEndpoints(
new SpecializedTestAnnotationEndpointDiscoverer(context)
.discoverEndpoints());
Map<Method, TestEndpointOperation> operations = mapOperations(
endpoints.get("specialized"));
assertThat(operations).containsKeys(
ReflectionUtils.findMethod(SpecializedExtension.class, "getSpecial"));
});
}
@Test
public void filtersAreApplied() {
load(TestEndpointsConfiguration.class, (context) -> {
EndpointFilter<SpecializedTestEndpointOperation> filter = (info,
discoverer) -> !(info.getId().equals("specialized"));
Map<String, EndpointInfo<SpecializedTestEndpointOperation>> endpoints = mapEndpoints(
new SpecializedTestAnnotationEndpointDiscoverer(context,
Collections.singleton(filter)).discoverEndpoints());
assertThat(endpoints).containsOnlyKeys("test");
});
}
private <T extends Operation> Map<String, EndpointInfo<T>> mapEndpoints(
Collection<EndpointInfo<T>> endpoints) {
Map<String, EndpointInfo<T>> endpointById = new LinkedHashMap<>();
endpoints.forEach((endpoint) -> {
EndpointInfo<T> existing = endpointById.put(endpoint.getId(), endpoint);
if (existing != null) {
throw new AssertionError(String.format(
"Found endpoints with duplicate id '%s'", endpoint.getId()));
}
});
return endpointById;
}
private Map<Method, TestEndpointOperation> mapOperations(
EndpointInfo<? extends TestEndpointOperation> endpoint) {
Map<Method, TestEndpointOperation> operationByMethod = new HashMap<>();
endpoint.getOperations().forEach((operation) -> {
Method method = operation.getMethodInfo().getMethod();
Operation existing = operationByMethod.put(method, operation);
if (existing != null) {
throw new AssertionError(String.format(
"Found endpoint with duplicate operation method '%s'", method));
}
});
return operationByMethod;
}
private void load(Class<?> configuration,
Consumer<AnnotationConfigApplicationContext> consumer) {
doLoad(null, configuration, consumer);
}
private void loadWithParent(ApplicationContext parent, Class<?> configuration,
Consumer<AnnotationConfigApplicationContext> consumer) {
doLoad(parent, configuration, consumer);
}
private void doLoad(ApplicationContext parent, Class<?> configuration,
Consumer<AnnotationConfigApplicationContext> consumer) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
if (parent != null) {
context.setParent(parent);
}
context.register(configuration);
context.refresh();
try {
consumer.accept(context);
}
finally {
context.close();
}
}
@Configuration
static class EmptyConfiguration {
}
@Endpoint(id = "test")
static class TestEndpoint {
@ReadOperation
public Object getAll() {
return null;
}
@ReadOperation
public Object getOne(@Selector String id) {
return null;
}
@WriteOperation
public void update(String foo, String bar) {
}
@DeleteOperation
public void deleteOne(@Selector String id) {
}
public void someOtherMethod() {
}
}
@SpecializedEndpoint(id = "specialized")
static class SpecializedTestEndpoint {
@ReadOperation
public Object getAll() {
return null;
}
}
static class TestEndpointSubclass extends TestEndpoint {
@WriteOperation
public void updateWithMoreArguments(String foo, String bar, String baz) {
}
}
@Configuration
static class TestEndpointConfiguration {
@Bean
public TestEndpoint testEndpoint() {
return new TestEndpoint();
}
}
@Configuration
static class TestEndpointSubclassConfiguration {
@Bean
public TestEndpointSubclass testEndpointSubclass() {
return new TestEndpointSubclass();
}
}
@Import({ TestEndpoint.class, SpecializedTestEndpoint.class,
SpecializedExtension.class })
static class TestEndpointsConfiguration {
}
@Configuration
static class ClashingEndpointConfiguration {
@Bean
public TestEndpoint testEndpointTwo() {
return new TestEndpoint();
}
@Bean
public TestEndpoint testEndpointOne() {
return new TestEndpoint();
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Endpoint
@FilteredEndpoint(SpecializedEndpointFilter.class)
public @interface SpecializedEndpoint {
@AliasFor(annotation = Endpoint.class)
String id();
}
@EndpointExtension(endpoint = SpecializedTestEndpoint.class, filter = SpecializedEndpointFilter.class)
public static class SpecializedExtension {
@ReadOperation
public Object getSpecial() {
return null;
}
}
static class SpecializedEndpointFilter
implements EndpointFilter<SpecializedTestEndpointOperation> {
@Override
public boolean match(EndpointInfo<SpecializedTestEndpointOperation> info,
EndpointDiscoverer<SpecializedTestEndpointOperation> discoverer) {
return discoverer instanceof SpecializedTestAnnotationEndpointDiscoverer;
}
}
public static class TestAnnotationEndpointDiscoverer
extends AnnotationEndpointDiscoverer<Method, TestEndpointOperation> {
TestAnnotationEndpointDiscoverer(ApplicationContext applicationContext) {
this(applicationContext, (id) -> null, null);
}
TestAnnotationEndpointDiscoverer(ApplicationContext applicationContext,
Function<String, Long> timeToLive) {
this(applicationContext, timeToLive, null);
}
TestAnnotationEndpointDiscoverer(ApplicationContext applicationContext,
Function<String, Long> timeToLive,
Collection<? extends EndpointFilter<TestEndpointOperation>> filters) {
super(applicationContext, TestEndpointOperation::new,
TestEndpointOperation::getMethod,
new ConversionServiceParameterMapper(),
Collections.singleton(new CachingOperationInvokerAdvisor(timeToLive)),
filters);
}
}
public static class SpecializedTestAnnotationEndpointDiscoverer extends
AnnotationEndpointDiscoverer<Method, SpecializedTestEndpointOperation> {
SpecializedTestAnnotationEndpointDiscoverer(
ApplicationContext applicationContext) {
this(applicationContext, (id) -> null, null);
}
SpecializedTestAnnotationEndpointDiscoverer(ApplicationContext applicationContext,
Collection<? extends EndpointFilter<SpecializedTestEndpointOperation>> filters) {
this(applicationContext, (id) -> null, filters);
}
SpecializedTestAnnotationEndpointDiscoverer(ApplicationContext applicationContext,
Function<String, Long> timeToLive,
Collection<? extends EndpointFilter<SpecializedTestEndpointOperation>> filters) {
super(applicationContext, SpecializedTestEndpointOperation::new,
SpecializedTestEndpointOperation::getMethod,
new ConversionServiceParameterMapper(),
Collections.singleton(new CachingOperationInvokerAdvisor(timeToLive)),
filters);
}
}
public static class TestEndpointOperation extends Operation {
private final OperationMethodInfo methodInfo;
public TestEndpointOperation(String endpointId, OperationMethodInfo methodInfo,
Object target, OperationInvoker invoker) {
super(methodInfo.getOperationType(), invoker, true);
this.methodInfo = methodInfo;
}
public Method getMethod() {
return this.methodInfo.getMethod();
}
public OperationMethodInfo getMethodInfo() {
return this.methodInfo;
}
}
public static class SpecializedTestEndpointOperation extends TestEndpointOperation {
public SpecializedTestEndpointOperation(String endpointId,
OperationMethodInfo methodInfo, Object target, OperationInvoker invoker) {
super(endpointId, methodInfo, target, invoker);
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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 org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DiscoveredOperationMethod}.
*
* @author Phillip Webb
*/
public class DiscoveredOperationMethodTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void createWhenAnnotationAttributesIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("AnnotationAttributes must not be null");
Method method = ReflectionUtils.findMethod(getClass(), "example");
new DiscoveredOperationMethod(method, OperationType.READ, null);
}
@Test
public void getProducesMediaTypesShouldReturnMediaTypes() {
Method method = ReflectionUtils.findMethod(getClass(), "example");
AnnotationAttributes annotationAttributes = new AnnotationAttributes();
String[] produces = new String[] { "application/json" };
annotationAttributes.put("produces", produces);
DiscoveredOperationMethod discovered = new DiscoveredOperationMethod(method,
OperationType.READ, annotationAttributes);
assertThat(discovered.getProducesMediaTypes())
.containsExactly("application/json");
}
public void example() {
}
}

View File

@@ -0,0 +1,234 @@
/*
* 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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.invoke.OperationParameters;
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
import org.springframework.boot.actuate.endpoint.invoke.reflect.OperationMethod;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DiscoveredOperationsFactory}.
*
* @author Phillip Webb
*/
public class DiscoveredOperationsFactoryTests {
private TestDiscoveredOperationsFactory factory;
private ParameterValueMapper parameterValueMapper;
private List<OperationInvokerAdvisor> invokerAdvisors;
@Before
public void setup() {
this.parameterValueMapper = (parameter, value) -> value.toString();
this.invokerAdvisors = new ArrayList<>();
this.factory = new TestDiscoveredOperationsFactory(this.parameterValueMapper,
this.invokerAdvisors);
}
@Test
public void createOperationsWhenHasReadMethodShouldCreateOperation() {
Collection<TestOperation> operations = this.factory.createOperations("test",
new ExampleRead());
assertThat(operations).hasSize(1);
TestOperation operation = getFirst(operations);
assertThat(operation.getType()).isEqualTo(OperationType.READ);
}
@Test
public void createOperationsWhenHasWriteMethodShouldCreateOperation() {
Collection<TestOperation> operations = this.factory.createOperations("test",
new ExampleWrite());
assertThat(operations).hasSize(1);
TestOperation operation = getFirst(operations);
assertThat(operation.getType()).isEqualTo(OperationType.WRITE);
}
@Test
public void createOperationsWhenHasDeleteMethodShouldCreateOperation() {
Collection<TestOperation> operations = this.factory.createOperations("test",
new ExampleDelete());
assertThat(operations).hasSize(1);
TestOperation operation = getFirst(operations);
assertThat(operation.getType()).isEqualTo(OperationType.DELETE);
}
@Test
public void createOperationsWhenMultipleShouldReturnMultiple() {
Collection<TestOperation> operations = this.factory.createOperations("test",
new ExampleMultiple());
assertThat(operations).hasSize(2);
assertThat(operations.stream().map(TestOperation::getType))
.containsOnly(OperationType.READ, OperationType.WRITE);
}
@Test
public void createOperationsShouldProvideOperationMethod() {
TestOperation operation = getFirst(
this.factory.createOperations("test", new ExampleWithParams()));
OperationMethod operationMethod = operation.getOperationMethod();
assertThat(operationMethod.getMethod().getName()).isEqualTo("read");
assertThat(operationMethod.getParameters().hasParameters()).isTrue();
}
@Test
public void createOperationsShouldProviderInvoker() {
TestOperation operation = getFirst(
this.factory.createOperations("test", new ExampleWithParams()));
Map<String, Object> params = Collections.singletonMap("name", 123);
Object result = operation.invoke(params);
assertThat(result).isEqualTo("123");
}
@Test
public void createOperationShouldApplyAdvisors() {
TestOperationInvokerAdvisor advisor = new TestOperationInvokerAdvisor();
this.invokerAdvisors.add(advisor);
TestOperation operation = getFirst(
this.factory.createOperations("test", new ExampleRead()));
operation.invoke(Collections.emptyMap());
assertThat(advisor.getEndpointId()).isEqualTo("test");
assertThat(advisor.getOperationType()).isEqualTo(OperationType.READ);
assertThat(advisor.getParameters()).isEmpty();
}
private <T> T getFirst(Iterable<T> iterable) {
return iterable.iterator().next();
}
static class ExampleRead {
@ReadOperation
public String read() {
return "read";
}
}
static class ExampleWrite {
@WriteOperation
public String write() {
return "write";
}
}
static class ExampleDelete {
@DeleteOperation
public String delete() {
return "delete";
}
}
static class ExampleMultiple {
@ReadOperation
public String read() {
return "read";
}
@WriteOperation
public String write() {
return "write";
}
}
static class ExampleWithParams {
@ReadOperation
public String read(String name) {
return name;
}
}
static class TestDiscoveredOperationsFactory
extends DiscoveredOperationsFactory<TestOperation> {
TestDiscoveredOperationsFactory(ParameterValueMapper parameterValueMapper,
Collection<OperationInvokerAdvisor> invokerAdvisors) {
super(parameterValueMapper, invokerAdvisors);
}
@Override
protected TestOperation createOperation(String endpointId,
DiscoveredOperationMethod operationMethod, OperationInvoker invoker) {
return new TestOperation(endpointId, operationMethod, invoker);
}
}
static class TestOperation extends AbstractDiscoveredOperation {
TestOperation(String endpointId, DiscoveredOperationMethod operationMethod,
OperationInvoker invoker) {
super(operationMethod, invoker);
}
}
static class TestOperationInvokerAdvisor implements OperationInvokerAdvisor {
private String endpointId;
private OperationType operationType;
private OperationParameters parameters;
@Override
public OperationInvoker apply(String endpointId, OperationType operationType,
OperationParameters parameters, OperationInvoker invoker) {
this.endpointId = endpointId;
this.operationType = operationType;
this.parameters = parameters;
return invoker;
}
public String getEndpointId() {
return this.endpointId;
}
public OperationType getOperationType() {
return this.operationType;
}
public OperationParameters getParameters() {
return this.parameters;
}
}
}

View File

@@ -0,0 +1,109 @@
/*
* 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.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.actuate.endpoint.EndpointFilter;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.Operation;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
import org.springframework.context.ApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link DiscovererEndpointFilter}.
*
* @author Phillip Webb
*/
public class DiscovererEndpointFilterTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void createWhenDiscovererIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Discoverer must not be null");
new TestDiscovererEndpointFilter(null);
}
@Test
public void matchWhenDiscoveredByDiscovererShouldReturnTrue() {
DiscovererEndpointFilter filter = new TestDiscovererEndpointFilter(
TestDiscovererA.class);
DiscoveredEndpoint<?> endpoint = mockDiscoveredEndpoint(TestDiscovererA.class);
assertThat(filter.match(endpoint)).isTrue();
}
@Test
public void matchWhenNotDiscoveredByDiscovererShouldReturnFalse() {
DiscovererEndpointFilter filter = new TestDiscovererEndpointFilter(
TestDiscovererA.class);
DiscoveredEndpoint<?> endpoint = mockDiscoveredEndpoint(TestDiscovererB.class);
assertThat(filter.match(endpoint)).isFalse();
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private DiscoveredEndpoint<?> mockDiscoveredEndpoint(Class<?> discoverer) {
DiscoveredEndpoint endpoint = mock(DiscoveredEndpoint.class);
given(endpoint.wasDiscoveredBy(discoverer)).willReturn(true);
return endpoint;
}
static class TestDiscovererEndpointFilter extends DiscovererEndpointFilter {
TestDiscovererEndpointFilter(
Class<? extends EndpointDiscoverer<?, ?>> discoverer) {
super(discoverer);
}
}
abstract static class TestDiscovererA
extends EndpointDiscoverer<ExposableEndpoint<Operation>, Operation> {
TestDiscovererA(ApplicationContext applicationContext,
ParameterValueMapper parameterValueMapper,
Collection<OperationInvokerAdvisor> invokerAdvisors,
Collection<EndpointFilter<ExposableEndpoint<Operation>>> filters) {
super(applicationContext, parameterValueMapper, invokerAdvisors, filters);
}
}
abstract static class TestDiscovererB
extends EndpointDiscoverer<ExposableEndpoint<Operation>, Operation> {
TestDiscovererB(ApplicationContext applicationContext,
ParameterValueMapper parameterValueMapper,
Collection<OperationInvokerAdvisor> invokerAdvisors,
Collection<EndpointFilter<ExposableEndpoint<Operation>>> filters) {
super(applicationContext, parameterValueMapper, invokerAdvisors, filters);
}
}
}

View File

@@ -0,0 +1,579 @@
/*
* 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.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.actuate.endpoint.EndpointFilter;
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.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
import org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvoker;
import org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AliasFor;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link EndpointDiscoverer}.
*
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Phillip Webb
*/
public class EndpointDiscovererTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Test
public void createWhenApplicationContextIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("ApplicationContext must not be null");
new TestEndpointDiscoverer(null, mock(ParameterValueMapper.class),
Collections.emptyList(), Collections.emptyList());
}
@Test
public void createWhenParameterValueMapperIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("ParameterValueMapper must not be null");
new TestEndpointDiscoverer(mock(ApplicationContext.class), null,
Collections.emptyList(), Collections.emptyList());
}
@Test
public void createWhenInvokerAdvisorsIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("InvokerAdvisors must not be null");
new TestEndpointDiscoverer(mock(ApplicationContext.class),
mock(ParameterValueMapper.class), null, Collections.emptyList());
}
@Test
public void createWhenFiltersIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Filters must not be null");
new TestEndpointDiscoverer(mock(ApplicationContext.class),
mock(ParameterValueMapper.class), Collections.emptyList(), null);
}
@Test
public void getEndpointsWhenNoEndpointBeansShouldReturnEmptyCollection() {
load(EmptyConfiguration.class, (context) -> {
TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context);
Collection<TestExposableEndpoint> endpoints = discoverer.getEndpoints();
assertThat(endpoints).isEmpty();
});
}
@Test
public void getEndpointsWhenHasEndpointShouldReturnEndpoint() {
load(TestEndpointConfiguration.class, this::hasTestEndpoint);
}
@Test
public void getEndpointsWhenHasEndpointInParentContextShouldReturnEndpoint() {
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext(
TestEndpointConfiguration.class);
loadWithParent(parent, EmptyConfiguration.class, this::hasTestEndpoint);
}
@Test
public void getEndpointsWhenHasSubclassedEndpointShouldReturnEndpoint() {
load(TestEndpointSubclassConfiguration.class, (context) -> {
TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context);
Map<String, TestExposableEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test");
Map<Method, TestOperation> operations = mapOperations(endpoints.get("test"));
assertThat(operations).hasSize(5);
assertThat(operations).containsKeys(testEndpointMethods());
assertThat(operations).containsKeys(ReflectionUtils.findMethod(
TestEndpointSubclass.class, "updateWithMoreArguments", String.class,
String.class, String.class));
});
}
@Test
public void getEndpointsWhenTwoEndpointsHaveTheSameIdShouldThrowException() {
load(ClashingEndpointConfiguration.class, (context) -> {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Found two endpoints with the id 'test': ");
new TestEndpointDiscoverer(context).getEndpoints();
});
}
@Test
public void getEndpointsWhenTtlSetToZeroShouldNotCacheInvokeCalls() {
load(TestEndpointConfiguration.class, (context) -> {
TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context,
(endpointId) -> 0L);
Map<String, TestExposableEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test");
Map<Method, TestOperation> operations = mapOperations(endpoints.get("test"));
operations.values().forEach((operation) -> assertThat(operation.getInvoker())
.isNotInstanceOf(CachingOperationInvoker.class));
});
}
@Test
public void getEndpointsWhenTtlSetByIdAndIdDoesntMatchShouldNotCacheInvokeCalls() {
load(TestEndpointConfiguration.class, (context) -> {
TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context,
(endpointId) -> (endpointId.equals("foo") ? 500L : 0L));
Map<String, TestExposableEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test");
Map<Method, TestOperation> operations = mapOperations(endpoints.get("test"));
operations.values().forEach((operation) -> assertThat(operation.getInvoker())
.isNotInstanceOf(CachingOperationInvoker.class));
});
}
@Test
public void getEndpointsWhenTtlSetByIdAndIdMatchesShouldCacheInvokeCalls() {
load(TestEndpointConfiguration.class, (context) -> {
TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context,
(endpointId) -> (endpointId.equals("test") ? 500L : 0L));
Map<String, TestExposableEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test");
Map<Method, TestOperation> operations = mapOperations(endpoints.get("test"));
TestOperation getAll = operations.get(findTestEndpointMethod("getAll"));
TestOperation getOne = operations
.get(findTestEndpointMethod("getOne", String.class));
TestOperation update = operations.get(ReflectionUtils.findMethod(
TestEndpoint.class, "update", String.class, String.class));
assertThat(((CachingOperationInvoker) getAll.getInvoker()).getTimeToLive())
.isEqualTo(500);
assertThat(getOne.getInvoker())
.isNotInstanceOf(CachingOperationInvoker.class);
assertThat(update.getInvoker())
.isNotInstanceOf(CachingOperationInvoker.class);
});
}
@Test
public void getEndpointsWhenHasSpecializedFiltersInNonSpecializedDiscovererShouldFilterEndpoints() {
load(SpecializedEndpointsConfiguration.class, (context) -> {
TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context);
Map<String, TestExposableEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test");
});
}
@Test
public void getEndpointsWhenHasSpecializedFiltersInSpecializedDiscovererShouldNotFilterEndpoints() {
load(SpecializedEndpointsConfiguration.class, (context) -> {
SpecializedEndpointDiscoverer discoverer = new SpecializedEndpointDiscoverer(
context);
Map<String, SpecializedExposableEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test", "specialized");
});
}
@Test
public void getEndpointsShouldApplyExtensions() {
load(SpecializedEndpointsConfiguration.class, (context) -> {
SpecializedEndpointDiscoverer discoverer = new SpecializedEndpointDiscoverer(
context);
Map<String, SpecializedExposableEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints());
Map<Method, SpecializedOperation> operations = mapOperations(
endpoints.get("specialized"));
assertThat(operations).containsKeys(
ReflectionUtils.findMethod(SpecializedExtension.class, "getSpecial"));
});
}
@Test
public void getEndpointsShouldApplyFilters() {
load(SpecializedEndpointsConfiguration.class, (context) -> {
EndpointFilter<SpecializedExposableEndpoint> filter = (endpoint) -> {
String id = endpoint.getId();
return !id.equals("specialized");
};
SpecializedEndpointDiscoverer discoverer = new SpecializedEndpointDiscoverer(
context, Collections.singleton(filter));
Map<String, SpecializedExposableEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test");
});
}
private void hasTestEndpoint(AnnotationConfigApplicationContext context) {
TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context);
Map<String, TestExposableEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test");
Map<Method, TestOperation> operations = mapOperations(endpoints.get("test"));
assertThat(operations).hasSize(4);
assertThat(operations).containsKeys();
}
private Method[] testEndpointMethods() {
List<Method> methods = new ArrayList<>();
methods.add(findTestEndpointMethod("getAll"));
methods.add(findTestEndpointMethod("getOne", String.class));
methods.add(findTestEndpointMethod("update", String.class, String.class));
methods.add(findTestEndpointMethod("deleteOne", String.class));
return methods.toArray(new Method[] {});
}
private Method findTestEndpointMethod(String name, Class<?>... paramTypes) {
return ReflectionUtils.findMethod(TestEndpoint.class, name, paramTypes);
}
private <E extends ExposableEndpoint<?>> Map<String, E> mapEndpoints(
Collection<E> endpoints) {
Map<String, E> byId = new LinkedHashMap<>();
endpoints.forEach((endpoint) -> {
E existing = byId.put(endpoint.getId(), endpoint);
if (existing != null) {
throw new AssertionError(String.format(
"Found endpoints with duplicate id '%s'", endpoint.getId()));
}
});
return byId;
}
private <O extends Operation> Map<Method, O> mapOperations(
ExposableEndpoint<O> endpoint) {
Map<Method, O> byMethod = new HashMap<>();
endpoint.getOperations().forEach((operation) -> {
AbstractDiscoveredOperation discoveredOperation = (AbstractDiscoveredOperation) operation;
Method method = discoveredOperation.getOperationMethod().getMethod();
O existing = byMethod.put(method, operation);
if (existing != null) {
throw new AssertionError(String.format(
"Found endpoint with duplicate operation method '%s'", method));
}
});
return byMethod;
}
private void load(Class<?> configuration,
Consumer<AnnotationConfigApplicationContext> consumer) {
load(null, configuration, consumer);
}
private void loadWithParent(ApplicationContext parent, Class<?> configuration,
Consumer<AnnotationConfigApplicationContext> consumer) {
load(parent, configuration, consumer);
}
private void load(ApplicationContext parent, Class<?> configuration,
Consumer<AnnotationConfigApplicationContext> consumer) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
if (parent != null) {
context.setParent(parent);
}
context.register(configuration);
context.refresh();
try {
consumer.accept(context);
}
finally {
context.close();
}
}
@Configuration
static class EmptyConfiguration {
}
@Configuration
static class TestEndpointConfiguration {
@Bean
public TestEndpoint testEndpoint() {
return new TestEndpoint();
}
}
@Configuration
static class TestEndpointSubclassConfiguration {
@Bean
public TestEndpointSubclass testEndpointSubclass() {
return new TestEndpointSubclass();
}
}
@Configuration
static class ClashingEndpointConfiguration {
@Bean
public TestEndpoint testEndpointTwo() {
return new TestEndpoint();
}
@Bean
public TestEndpoint testEndpointOne() {
return new TestEndpoint();
}
}
@Import({ TestEndpoint.class, SpecializedTestEndpoint.class,
SpecializedExtension.class })
static class SpecializedEndpointsConfiguration {
}
@Endpoint(id = "test")
static class TestEndpoint {
@ReadOperation
public Object getAll() {
return null;
}
@ReadOperation
public Object getOne(@Selector String id) {
return null;
}
@WriteOperation
public void update(String foo, String bar) {
}
@DeleteOperation
public void deleteOne(@Selector String id) {
}
public void someOtherMethod() {
}
}
static class TestEndpointSubclass extends TestEndpoint {
@WriteOperation
public void updateWithMoreArguments(String foo, String bar, String baz) {
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Endpoint
@FilteredEndpoint(SpecializedEndpointFilter.class)
public @interface SpecializedEndpoint {
@AliasFor(annotation = Endpoint.class)
String id();
}
@EndpointExtension(endpoint = SpecializedTestEndpoint.class, filter = SpecializedEndpointFilter.class)
public static class SpecializedExtension {
@ReadOperation
public Object getSpecial() {
return null;
}
}
static class SpecializedEndpointFilter extends DiscovererEndpointFilter {
SpecializedEndpointFilter() {
super(SpecializedEndpointDiscoverer.class);
}
}
@SpecializedEndpoint(id = "specialized")
static class SpecializedTestEndpoint {
@ReadOperation
public Object getAll() {
return null;
}
}
static class TestEndpointDiscoverer
extends EndpointDiscoverer<TestExposableEndpoint, TestOperation> {
TestEndpointDiscoverer(ApplicationContext applicationContext) {
this(applicationContext, (id) -> null);
}
TestEndpointDiscoverer(ApplicationContext applicationContext,
Function<String, Long> timeToLive) {
this(applicationContext, timeToLive, Collections.emptyList());
}
TestEndpointDiscoverer(ApplicationContext applicationContext,
Function<String, Long> timeToLive,
Collection<EndpointFilter<TestExposableEndpoint>> filters) {
this(applicationContext, new ConversionServiceParameterValueMapper(),
Collections.singleton(new CachingOperationInvokerAdvisor(timeToLive)),
filters);
}
TestEndpointDiscoverer(ApplicationContext applicationContext,
ParameterValueMapper parameterValueMapper,
Collection<OperationInvokerAdvisor> invokerAdvisors,
Collection<EndpointFilter<TestExposableEndpoint>> filters) {
super(applicationContext, parameterValueMapper, invokerAdvisors, filters);
}
@Override
protected TestExposableEndpoint createEndpoint(String id,
boolean enabledByDefault, Collection<TestOperation> operations) {
return new TestExposableEndpoint(this, id, enabledByDefault, operations);
}
@Override
protected TestOperation createOperation(String endpointId,
DiscoveredOperationMethod operationMethod, OperationInvoker invoker) {
return new TestOperation(operationMethod, invoker);
}
@Override
protected OperationKey createOperationKey(TestOperation operation) {
return new OperationKey(operation.getOperationMethod(),
() -> "TestOperation " + operation.getOperationMethod());
}
}
static class SpecializedEndpointDiscoverer extends
EndpointDiscoverer<SpecializedExposableEndpoint, SpecializedOperation> {
SpecializedEndpointDiscoverer(ApplicationContext applicationContext) {
this(applicationContext, Collections.emptyList());
}
SpecializedEndpointDiscoverer(ApplicationContext applicationContext,
Collection<EndpointFilter<SpecializedExposableEndpoint>> filters) {
super(applicationContext, new ConversionServiceParameterValueMapper(),
Collections.emptyList(), filters);
}
@Override
protected SpecializedExposableEndpoint createEndpoint(String id,
boolean enabledByDefault, Collection<SpecializedOperation> operations) {
return new SpecializedExposableEndpoint(this, id, enabledByDefault,
operations);
}
@Override
protected SpecializedOperation createOperation(String endpointId,
DiscoveredOperationMethod operationMethod, OperationInvoker invoker) {
return new SpecializedOperation(operationMethod, invoker);
}
@Override
protected OperationKey createOperationKey(SpecializedOperation operation) {
return new OperationKey(operation.getOperationMethod(),
() -> "TestOperation " + operation.getOperationMethod());
}
}
static class TestExposableEndpoint extends AbstractDiscoveredEndpoint<TestOperation> {
TestExposableEndpoint(EndpointDiscoverer<?, ?> discoverer, String id,
boolean enabledByDefault,
Collection<? extends TestOperation> operations) {
super(discoverer, id, enabledByDefault, operations);
}
}
static class SpecializedExposableEndpoint
extends AbstractDiscoveredEndpoint<SpecializedOperation> {
SpecializedExposableEndpoint(EndpointDiscoverer<?, ?> discoverer, String id,
boolean enabledByDefault,
Collection<? extends SpecializedOperation> operations) {
super(discoverer, id, enabledByDefault, operations);
}
}
static class TestOperation extends AbstractDiscoveredOperation {
private final OperationInvoker invoker;
TestOperation(DiscoveredOperationMethod operationMethod,
OperationInvoker invoker) {
super(operationMethod, invoker);
this.invoker = invoker;
}
public OperationInvoker getInvoker() {
return this.invoker;
}
}
static class SpecializedOperation extends TestOperation {
SpecializedOperation(DiscoveredOperationMethod operationMethod,
OperationInvoker invoker) {
super(operationMethod, invoker);
}
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint.convert;
package org.springframework.boot.actuate.endpoint.invoke.convert;
import java.time.OffsetDateTime;
@@ -22,7 +22,8 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.actuate.endpoint.reflect.ParameterMappingException;
import org.springframework.boot.actuate.endpoint.invoke.OperationParameter;
import org.springframework.boot.actuate.endpoint.invoke.ParameterMappingException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.format.support.DefaultFormattingConversionService;
@@ -36,11 +37,11 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link ConversionServiceParameterMapper}.
* Tests for {@link ConversionServiceParameterValueMapper}.
*
* @author Phillip Webb
*/
public class ConversionServiceParameterMapperTests {
public class ConversionServiceParameterValueMapperTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@@ -49,9 +50,10 @@ public class ConversionServiceParameterMapperTests {
public void mapParameterShouldDelegateToConversionService() {
DefaultFormattingConversionService conversionService = spy(
new DefaultFormattingConversionService());
ConversionServiceParameterMapper mapper = new ConversionServiceParameterMapper(
ConversionServiceParameterValueMapper mapper = new ConversionServiceParameterValueMapper(
conversionService);
Integer mapped = mapper.mapParameter("123", Integer.class);
Object mapped = mapper
.mapParameterValue(new TestOperationParameter(Integer.class), "123");
assertThat(mapped).isEqualTo(123);
verify(conversionService).convert("123", Integer.class);
}
@@ -61,34 +63,61 @@ public class ConversionServiceParameterMapperTests {
ConversionService conversionService = mock(ConversionService.class);
RuntimeException error = new RuntimeException();
given(conversionService.convert(any(), any())).willThrow(error);
ConversionServiceParameterMapper mapper = new ConversionServiceParameterMapper(
ConversionServiceParameterValueMapper mapper = new ConversionServiceParameterValueMapper(
conversionService);
try {
mapper.mapParameter("123", Integer.class);
mapper.mapParameterValue(new TestOperationParameter(Integer.class), "123");
fail("Did not throw");
}
catch (ParameterMappingException ex) {
assertThat(ex.getInput()).isEqualTo("123");
assertThat(ex.getType()).isEqualTo(Integer.class);
assertThat(ex.getValue()).isEqualTo("123");
assertThat(ex.getParameter().getType()).isEqualTo(Integer.class);
assertThat(ex.getCause()).isEqualTo(error);
}
}
@Test
public void createShouldRegisterIsoOffsetDateTimeConverter() {
ConversionServiceParameterMapper mapper = new ConversionServiceParameterMapper();
OffsetDateTime mapped = mapper.mapParameter("2011-12-03T10:15:30+01:00",
OffsetDateTime.class);
ConversionServiceParameterValueMapper mapper = new ConversionServiceParameterValueMapper();
Object mapped = mapper.mapParameterValue(
new TestOperationParameter(OffsetDateTime.class),
"2011-12-03T10:15:30+01:00");
assertThat(mapped).isNotNull();
}
@Test
public void createWithConversionServiceShouldNotRegisterIsoOffsetDateTimeConverter() {
ConversionService conversionService = new DefaultConversionService();
ConversionServiceParameterMapper mapper = new ConversionServiceParameterMapper(
ConversionServiceParameterValueMapper mapper = new ConversionServiceParameterValueMapper(
conversionService);
this.thrown.expect(ParameterMappingException.class);
mapper.mapParameter("2011-12-03T10:15:30+01:00", OffsetDateTime.class);
mapper.mapParameterValue(new TestOperationParameter(OffsetDateTime.class),
"2011-12-03T10:15:30+01:00");
}
private static class TestOperationParameter implements OperationParameter {
private final Class<?> type;
TestOperationParameter(Class<?> type) {
this.type = type;
}
@Override
public String getName() {
return "test";
}
@Override
public Class<?> getType() {
return this.type;
}
@Override
public boolean isNullable() {
return true;
}
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint.convert;
package org.springframework.boot.actuate.endpoint.invoke.convert;
import java.time.OffsetDateTime;

View File

@@ -0,0 +1,70 @@
/*
* 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 org.junit.Test;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link OperationMethodParameter}.
*
* @author Phillip Webb
*/
public class OperationMethodParameterTests {
private Method method = ReflectionUtils.findMethod(getClass(), "example",
String.class, String.class);
@Test
public void getNameShouldReturnName() {
OperationMethodParameter parameter = new OperationMethodParameter("name",
this.method.getParameters()[0]);
assertThat(parameter.getName()).isEqualTo("name");
}
@Test
public void getTypeShouldReturnTyoe() {
OperationMethodParameter parameter = new OperationMethodParameter("name",
this.method.getParameters()[0]);
assertThat(parameter.getType()).isEqualTo(String.class);
}
@Test
public void isNullableWhenNoAnnotationShouldReturnFalse() {
OperationMethodParameter parameter = new OperationMethodParameter("name",
this.method.getParameters()[0]);
assertThat(parameter.isNullable()).isFalse();
}
@Test
public void isNullableWhenNullableAnnotationShouldReturnTrue() {
OperationMethodParameter parameter = new OperationMethodParameter("name",
this.method.getParameters()[1]);
assertThat(parameter.isNullable()).isTrue();
}
void example(String one, @Nullable String two) {
}
}

View File

@@ -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.invoke.reflect;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.actuate.endpoint.invoke.OperationParameter;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link OperationMethodParameters}.
*
* @author Phillip Webb
*/
public class OperationMethodParametersTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private Method exampleMethod = ReflectionUtils.findMethod(getClass(), "example",
String.class);
private Method exampleNoParamsMethod = ReflectionUtils.findMethod(getClass(),
"exampleNoParams");
@Test
public void createWhenMethodIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Method must not be null");
new OperationMethodParameters(null, mock(ParameterNameDiscoverer.class));
}
@Test
public void createWhenParameterNameDiscovererIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("ParameterNameDiscoverer must not be null");
new OperationMethodParameters(this.exampleMethod, null);
}
@Test
public void createWhenParameterNameDiscovererReturnsNullShouldThrowException() {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Failed to extract parameter names");
new OperationMethodParameters(this.exampleMethod,
mock(ParameterNameDiscoverer.class));
}
@Test
public void hasParametersWhenHasParametersShouldReturnTrue() {
OperationMethodParameters parameters = new OperationMethodParameters(
this.exampleMethod, new DefaultParameterNameDiscoverer());
assertThat(parameters.hasParameters()).isTrue();
}
@Test
public void hasParametersWhenHasNoParametersShouldReturnFalse() {
OperationMethodParameters parameters = new OperationMethodParameters(
this.exampleNoParamsMethod, new DefaultParameterNameDiscoverer());
assertThat(parameters.hasParameters()).isFalse();
}
@Test
public void getParameterCountShouldReturnParameterCount() {
OperationMethodParameters parameters = new OperationMethodParameters(
this.exampleMethod, new DefaultParameterNameDiscoverer());
assertThat(parameters.getParameterCount()).isEqualTo(1);
}
@Test
public void iteratorShouldIterateOperationParameters() {
OperationMethodParameters parameters = new OperationMethodParameters(
this.exampleMethod, new DefaultParameterNameDiscoverer());
Iterator<OperationParameter> iterator = parameters.iterator();
assertParameters(StreamSupport.stream(
Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED),
false));
}
@Test
public void streamShouldStreamOperationParameters() {
OperationMethodParameters parameters = new OperationMethodParameters(
this.exampleMethod, new DefaultParameterNameDiscoverer());
assertParameters(parameters.stream());
}
private void assertParameters(Stream<OperationParameter> stream) {
List<OperationParameter> parameters = stream.collect(Collectors.toList());
assertThat(parameters).hasSize(1);
OperationParameter parameter = parameters.get(0);
assertThat(parameter.getName()).isEqualTo("name");
assertThat(parameter.getType()).isEqualTo(String.class);
}
String example(String name) {
return name;
}
String exampleNoParams() {
return "example";
}
}

View File

@@ -0,0 +1,84 @@
/*
* 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 org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.invoke.OperationParameters;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link OperationMethod}.
*
* @author Phillip Webb
*/
public class OperationMethodTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private Method exampleMethod = ReflectionUtils.findMethod(getClass(), "example",
String.class);
@Test
public void createWhenMethodIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Method must not be null");
new OperationMethod(null, OperationType.READ);
}
@Test
public void createWhenOperationTypeIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("OperationType must not be null");
new OperationMethod(this.exampleMethod, null);
}
@Test
public void getMethodShouldReturnMethod() {
OperationMethod operationMethod = new OperationMethod(this.exampleMethod,
OperationType.READ);
assertThat(operationMethod.getMethod()).isEqualTo(this.exampleMethod);
}
@Test
public void getOperationTypeShouldReturnOperationType() {
OperationMethod operationMethod = new OperationMethod(this.exampleMethod,
OperationType.READ);
assertThat(operationMethod.getOperationType()).isEqualTo(OperationType.READ);
}
@Test
public void getParametersShouldReturnParameters() {
OperationMethod operationMethod = new OperationMethod(this.exampleMethod,
OperationType.READ);
OperationParameters parameters = operationMethod.getParameters();
assertThat(parameters.getParameterCount()).isEqualTo(1);
assertThat(parameters.iterator().next().getName()).isEqualTo("name");
}
String example(String name) {
return name;
}
}

View File

@@ -0,0 +1,128 @@
/*
* 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.util.Collections;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.invoke.MissingParametersException;
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ReflectiveOperationInvoker}.
*
* @author Phillip Webb
*/
public class ReflectiveOperationInvokerTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private Example target;
private OperationMethod operationMethod;
private ParameterValueMapper parameterValueMapper;
@Before
public void setup() {
this.target = new Example();
this.operationMethod = new OperationMethod(
ReflectionUtils.findMethod(Example.class, "reverse", String.class),
OperationType.READ);
this.parameterValueMapper = (parameter,
value) -> (value == null ? null : value.toString());
}
@Test
public void createWhenTargetIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Target must not be null");
new ReflectiveOperationInvoker(null, this.operationMethod,
this.parameterValueMapper);
}
@Test
public void createWhenOperationMethodIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("OperationMethod must not be null");
new ReflectiveOperationInvoker(this.target, null, this.parameterValueMapper);
}
@Test
public void createWhenParamaterValueMapperIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("ParameterValueMapper must not be null");
new ReflectiveOperationInvoker(this.target, this.operationMethod, null);
}
@Test
public void invokeShouldInvokeMethod() {
ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(this.target,
this.operationMethod, this.parameterValueMapper);
Object result = invoker.invoke(Collections.singletonMap("name", "boot"));
assertThat(result).isEqualTo("toob");
}
@Test
public void invokeWhenMissingNonNullableArgmentShouldThrowException() {
ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(this.target,
this.operationMethod, this.parameterValueMapper);
this.thrown.expect(MissingParametersException.class);
invoker.invoke(Collections.singletonMap("name", null));
}
@Test
public void invokeWhenMissingNullableArgumentShouldInvoke() {
OperationMethod operationMethod = new OperationMethod(ReflectionUtils.findMethod(
Example.class, "reverseNullable", String.class), OperationType.READ);
ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(this.target,
operationMethod, this.parameterValueMapper);
Object result = invoker.invoke(Collections.singletonMap("name", null));
assertThat(result).isEqualTo("llun");
}
@Test
public void invokeShouldResolveParameters() {
ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(this.target,
this.operationMethod, this.parameterValueMapper);
Object result = invoker.invoke(Collections.singletonMap("name", 1234));
assertThat(result).isEqualTo("4321");
}
static class Example {
String reverse(String name) {
return new StringBuilder(name).reverse().toString();
}
String reverseNullable(@Nullable String name) {
return new StringBuilder(String.valueOf(name)).reverse().toString();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint.cache;
package org.springframework.boot.actuate.endpoint.invoker.cache;
import java.lang.reflect.Method;
import java.util.function.Function;
@@ -24,10 +24,10 @@ import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.actuate.endpoint.OperationInvoker;
import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.reflect.OperationMethodInfo;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
import org.springframework.boot.actuate.endpoint.invoke.OperationParameters;
import org.springframework.boot.actuate.endpoint.invoke.reflect.OperationMethod;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.ReflectionUtils;
@@ -59,53 +59,62 @@ public class CachingOperationInvokerAdvisorTests {
@Test
public void applyWhenOperationIsNotReadShouldNotAddAdvise() {
OperationMethodInfo info = mockInfo(OperationType.WRITE, "get");
OperationInvoker advised = this.advisor.apply("foo", info, this.invoker);
OperationParameters parameters = getParameters("get");
OperationInvoker advised = this.advisor.apply("foo", OperationType.WRITE,
parameters, this.invoker);
assertThat(advised).isSameAs(this.invoker);
}
@Test
public void applyWhenHasParametersShouldNotAddAdvise() {
OperationMethodInfo info = mockInfo(OperationType.READ, "getWithParameter",
String.class);
OperationInvoker advised = this.advisor.apply("foo", info, this.invoker);
OperationParameters parameters = getParameters("getWithParameter", String.class);
OperationInvoker advised = this.advisor.apply("foo", OperationType.READ,
parameters, this.invoker);
assertThat(advised).isSameAs(this.invoker);
}
@Test
public void applyWhenTimeToLiveReturnsNullShouldNotAddAdvise() {
OperationMethodInfo info = mockInfo(OperationType.READ, "get");
OperationParameters parameters = getParameters("get");
given(this.timeToLive.apply(any())).willReturn(null);
OperationInvoker advised = this.advisor.apply("foo", info, this.invoker);
OperationInvoker advised = this.advisor.apply("foo", OperationType.READ,
parameters, this.invoker);
assertThat(advised).isSameAs(this.invoker);
verify(this.timeToLive).apply("foo");
}
@Test
public void applyWhenTimeToLiveIsZeroShouldNotAddAdvise() {
OperationMethodInfo info = mockInfo(OperationType.READ, "get");
OperationParameters parameters = getParameters("get");
given(this.timeToLive.apply(any())).willReturn(0L);
OperationInvoker advised = this.advisor.apply("foo", info, this.invoker);
OperationInvoker advised = this.advisor.apply("foo", OperationType.READ,
parameters, this.invoker);
assertThat(advised).isSameAs(this.invoker);
verify(this.timeToLive).apply("foo");
}
@Test
public void applyShouldAddCacheAdvise() {
OperationMethodInfo info = mockInfo(OperationType.READ, "get");
OperationParameters parameters = getParameters("get");
given(this.timeToLive.apply(any())).willReturn(100L);
OperationInvoker advised = this.advisor.apply("foo", info, this.invoker);
OperationInvoker advised = this.advisor.apply("foo", OperationType.READ,
parameters, this.invoker);
assertThat(advised).isInstanceOf(CachingOperationInvoker.class);
assertThat(ReflectionTestUtils.getField(advised, "target"))
assertThat(ReflectionTestUtils.getField(advised, "invoker"))
.isEqualTo(this.invoker);
assertThat(ReflectionTestUtils.getField(advised, "timeToLive")).isEqualTo(100L);
}
private OperationMethodInfo mockInfo(OperationType operationType, String methodName,
private OperationParameters getParameters(String methodName,
Class<?>... parameterTypes) {
return getOperationMethod(methodName, parameterTypes).getParameters();
}
private OperationMethod getOperationMethod(String methodName,
Class<?>... parameterTypes) {
Method method = ReflectionUtils.findMethod(TestOperations.class, methodName,
parameterTypes);
return new OperationMethodInfo(method, operationType, new AnnotationAttributes());
return new OperationMethod(method, OperationType.READ);
}
public static class TestOperations {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint.cache;
package org.springframework.boot.actuate.endpoint.invoker.cache;
import java.util.HashMap;
import java.util.Map;
@@ -23,7 +23,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.actuate.endpoint.OperationInvoker;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
@@ -44,7 +44,7 @@ public class CachingOperationInvokerTests {
@Test
public void createInstanceWithTtlSetToZero() {
this.thrown.expect(IllegalStateException.class);
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("TimeToLive");
new CachingOperationInvoker(mock(OperationInvoker.class), 0);
}

View File

@@ -1,131 +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.Collections;
import java.util.Map;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import org.junit.Test;
import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.OperationInvoker;
import org.springframework.boot.actuate.endpoint.OperationType;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
/**
* Tests for {@link EndpointMBeanInfoAssembler}.
*
* @author Stephane Nicoll
*/
public class EndpointMBeanInfoAssemblerTests {
private final EndpointMBeanInfoAssembler mBeanInfoAssembler = new EndpointMBeanInfoAssembler(
new DummyOperationResponseMapper());
@Test
public void exposeSimpleReadOperation() {
JmxOperation operation = new JmxOperation(OperationType.READ,
new DummyOperationInvoker(), "getAll", Object.class, "Test operation",
Collections.emptyList());
EndpointInfo<JmxOperation> endpoint = new EndpointInfo<>("test", true,
Collections.singletonList(operation));
EndpointMBeanInfo endpointMBeanInfo = this.mBeanInfoAssembler
.createEndpointMBeanInfo(endpoint);
assertThat(endpointMBeanInfo).isNotNull();
assertThat(endpointMBeanInfo.getEndpointId()).isEqualTo("test");
assertThat(endpointMBeanInfo.getOperations())
.containsOnly(entry("getAll", operation));
MBeanInfo mbeanInfo = endpointMBeanInfo.getMbeanInfo();
assertThat(mbeanInfo).isNotNull();
assertThat(mbeanInfo.getClassName()).isEqualTo(EndpointMBean.class.getName());
assertThat(mbeanInfo.getDescription())
.isEqualTo("MBean operations for endpoint test");
assertThat(mbeanInfo.getAttributes()).isEmpty();
assertThat(mbeanInfo.getNotifications()).isEmpty();
assertThat(mbeanInfo.getConstructors()).isEmpty();
assertThat(mbeanInfo.getOperations()).hasSize(1);
MBeanOperationInfo mBeanOperationInfo = mbeanInfo.getOperations()[0];
assertThat(mBeanOperationInfo.getName()).isEqualTo("getAll");
assertThat(mBeanOperationInfo.getReturnType()).isEqualTo(Object.class.getName());
assertThat(mBeanOperationInfo.getImpact()).isEqualTo(MBeanOperationInfo.INFO);
assertThat(mBeanOperationInfo.getSignature()).hasSize(0);
}
@Test
public void exposeSimpleWriteOperation() {
JmxOperation operation = new JmxOperation(OperationType.WRITE,
new DummyOperationInvoker(), "update", Object.class, "Update operation",
Collections.singletonList(new JmxEndpointOperationParameterInfo("test",
String.class, "Test argument")));
EndpointInfo<JmxOperation> endpoint = new EndpointInfo<>("another", true,
Collections.singletonList(operation));
EndpointMBeanInfo endpointMBeanInfo = this.mBeanInfoAssembler
.createEndpointMBeanInfo(endpoint);
assertThat(endpointMBeanInfo).isNotNull();
assertThat(endpointMBeanInfo.getEndpointId()).isEqualTo("another");
assertThat(endpointMBeanInfo.getOperations())
.containsOnly(entry("update", operation));
MBeanInfo mbeanInfo = endpointMBeanInfo.getMbeanInfo();
assertThat(mbeanInfo).isNotNull();
assertThat(mbeanInfo.getClassName()).isEqualTo(EndpointMBean.class.getName());
assertThat(mbeanInfo.getDescription())
.isEqualTo("MBean operations for endpoint another");
assertThat(mbeanInfo.getAttributes()).isEmpty();
assertThat(mbeanInfo.getNotifications()).isEmpty();
assertThat(mbeanInfo.getConstructors()).isEmpty();
assertThat(mbeanInfo.getOperations()).hasSize(1);
MBeanOperationInfo mBeanOperationInfo = mbeanInfo.getOperations()[0];
assertThat(mBeanOperationInfo.getName()).isEqualTo("update");
assertThat(mBeanOperationInfo.getReturnType()).isEqualTo(Object.class.getName());
assertThat(mBeanOperationInfo.getImpact()).isEqualTo(MBeanOperationInfo.ACTION);
assertThat(mBeanOperationInfo.getSignature()).hasSize(1);
MBeanParameterInfo mBeanParameterInfo = mBeanOperationInfo.getSignature()[0];
assertThat(mBeanParameterInfo.getName()).isEqualTo("test");
assertThat(mBeanParameterInfo.getType()).isEqualTo(String.class.getName());
assertThat(mBeanParameterInfo.getDescription()).isEqualTo("Test argument");
}
private static class DummyOperationInvoker implements OperationInvoker {
@Override
public Object invoke(Map<String, Object> arguments) {
return null;
}
}
private static class DummyOperationResponseMapper
implements JmxOperationResponseMapper {
@Override
public Object mapResponse(Object response) {
return response;
}
@Override
public Class<?> mapResponseType(Class<?> responseType) {
return responseType;
}
}
}

View File

@@ -1,134 +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.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.jmx.JmxException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link EndpointMBeanRegistrar}.
*
* @author Stephane Nicoll
*/
public class EndpointMBeanRegistrarTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private MBeanServer mBeanServer = mock(MBeanServer.class);
@Test
public void mBeanServerMustNotBeNull() {
this.thrown.expect(IllegalArgumentException.class);
new EndpointMBeanRegistrar(null, (e) -> new ObjectName("foo"));
}
@Test
public void objectNameFactoryMustNotBeNull() {
this.thrown.expect(IllegalArgumentException.class);
new EndpointMBeanRegistrar(this.mBeanServer, null);
}
@Test
public void endpointMustNotBeNull() {
EndpointMBeanRegistrar registrar = new EndpointMBeanRegistrar(this.mBeanServer,
(e) -> new ObjectName("foo"));
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Endpoint must not be null");
registrar.registerEndpointMBean(null);
}
@Test
public void registerEndpointInvokesObjectNameFactory()
throws MalformedObjectNameException {
EndpointObjectNameFactory factory = mock(EndpointObjectNameFactory.class);
EndpointMBean endpointMBean = mock(EndpointMBean.class);
ObjectName objectName = mock(ObjectName.class);
given(factory.generate(endpointMBean)).willReturn(objectName);
EndpointMBeanRegistrar registrar = new EndpointMBeanRegistrar(this.mBeanServer,
factory);
ObjectName actualObjectName = registrar.registerEndpointMBean(endpointMBean);
assertThat(actualObjectName).isSameAs(objectName);
verify(factory).generate(endpointMBean);
}
@Test
public void registerEndpointInvalidObjectName() throws MalformedObjectNameException {
EndpointMBean endpointMBean = mock(EndpointMBean.class);
given(endpointMBean.getEndpointId()).willReturn("test");
EndpointObjectNameFactory factory = mock(EndpointObjectNameFactory.class);
given(factory.generate(endpointMBean))
.willThrow(new MalformedObjectNameException());
EndpointMBeanRegistrar registrar = new EndpointMBeanRegistrar(this.mBeanServer,
factory);
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Invalid ObjectName for endpoint with id 'test'");
registrar.registerEndpointMBean(endpointMBean);
}
@Test
public void registerEndpointFailure() throws Exception {
EndpointMBean endpointMBean = mock(EndpointMBean.class);
given(endpointMBean.getEndpointId()).willReturn("test");
EndpointObjectNameFactory factory = mock(EndpointObjectNameFactory.class);
ObjectName objectName = mock(ObjectName.class);
given(factory.generate(endpointMBean)).willReturn(objectName);
given(this.mBeanServer.registerMBean(endpointMBean, objectName))
.willThrow(MBeanRegistrationException.class);
EndpointMBeanRegistrar registrar = new EndpointMBeanRegistrar(this.mBeanServer,
factory);
this.thrown.expect(JmxException.class);
this.thrown.expectMessage("Failed to register MBean for endpoint with id 'test'");
registrar.registerEndpointMBean(endpointMBean);
}
@Test
public void unregisterEndpoint() throws Exception {
ObjectName objectName = mock(ObjectName.class);
EndpointMBeanRegistrar registrar = new EndpointMBeanRegistrar(this.mBeanServer,
mock(EndpointObjectNameFactory.class));
assertThat(registrar.unregisterEndpointMbean(objectName)).isTrue();
verify(this.mBeanServer).unregisterMBean(objectName);
}
@Test
public void unregisterUnknownEndpoint() throws Exception {
ObjectName objectName = mock(ObjectName.class);
willThrow(InstanceNotFoundException.class).given(this.mBeanServer)
.unregisterMBean(objectName);
EndpointMBeanRegistrar registrar = new EndpointMBeanRegistrar(this.mBeanServer,
mock(EndpointObjectNameFactory.class));
assertThat(registrar.unregisterEndpointMbean(objectName)).isFalse();
verify(this.mBeanServer).unregisterMBean(objectName);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,428 +16,136 @@
package org.springframework.boot.actuate.endpoint.jmx;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper;
import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxAnnotationEndpointDiscoverer;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link EndpointMBean}.
*
* @author Phillip Webb
* @author Stephane Nicoll
*/
public class EndpointMBeanTests {
private final JmxEndpointMBeanFactory jmxEndpointMBeanFactory = new JmxEndpointMBeanFactory(
new TestJmxOperationResponseMapper());
private static final Object[] NO_PARAMS = {};
private MBeanServer server;
private static final String[] NO_SIGNATURE = {};
private EndpointMBeanRegistrar endpointMBeanRegistrar;
@Rule
public ExpectedException thrown = ExpectedException.none();
private EndpointObjectNameFactory objectNameFactory = (endpoint) -> new ObjectName(
String.format("org.springframework.boot.test:type=Endpoint,name=%s",
UUID.randomUUID().toString()));
private TestExposableJmxEndpoint endpoint = new TestExposableJmxEndpoint(
new TestJmxOperation());
@Before
public void createMBeanServer() {
this.server = MBeanServerFactory.createMBeanServer();
this.endpointMBeanRegistrar = new EndpointMBeanRegistrar(this.server,
this.objectNameFactory);
}
private TestJmxOperationResponseMapper responseMapper = new TestJmxOperationResponseMapper();
@After
public void disposeMBeanServer() {
if (this.server != null) {
MBeanServerFactory.releaseMBeanServer(this.server);
}
@Test
public void createWhenResponseMapperIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("ResponseMapper must not be null");
new EndpointMBean(null, mock(ExposableJmxEndpoint.class));
}
@Test
public void invokeSimpleEndpoint() {
load(FooEndpoint.class, (discoverer) -> {
ObjectName objectName = registerEndpoint(discoverer, "foo");
try {
// getAll
Object allResponse = this.server.invoke(objectName, "getAll",
new Object[0], new String[0]);
assertThat(allResponse).isEqualTo("[ONE, TWO]");
// getOne
Object oneResponse = this.server.invoke(objectName, "getOne",
new Object[] { "one" }, new String[] { String.class.getName() });
assertThat(oneResponse).isEqualTo("ONE");
// update
Object updateResponse = this.server.invoke(objectName, "update",
new Object[] { "one", "1" },
new String[] { String.class.getName(), String.class.getName() });
assertThat(updateResponse).isNull();
// getOne validation after update
Object updatedOneResponse = this.server.invoke(objectName, "getOne",
new Object[] { "one" }, new String[] { String.class.getName() });
assertThat(updatedOneResponse).isEqualTo("1");
// deleteOne
Object deleteResponse = this.server.invoke(objectName, "deleteOne",
new Object[] { "one" }, new String[] { String.class.getName() });
assertThat(deleteResponse).isNull();
// getOne validation after delete
updatedOneResponse = this.server.invoke(objectName, "getOne",
new Object[] { "one" }, new String[] { String.class.getName() });
assertThat(updatedOneResponse).isNull();
}
catch (Exception ex) {
throw new AssertionError("Failed to invoke method on FooEndpoint", ex);
}
});
public void createWhenEndpointIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Endpoint must not be null");
new EndpointMBean(mock(JmxOperationResponseMapper.class), null);
}
@Test
public void jmxTypesAreProperlyMapped() {
load(FooEndpoint.class, (discoverer) -> {
ObjectName objectName = registerEndpoint(discoverer, "foo");
try {
MBeanInfo mBeanInfo = this.server.getMBeanInfo(objectName);
Map<String, MBeanOperationInfo> operations = mapOperations(mBeanInfo);
assertThat(operations).containsOnlyKeys("getAll", "getOne", "update",
"deleteOne");
assertOperation(operations.get("getAll"), String.class,
MBeanOperationInfo.INFO, new Class<?>[0]);
assertOperation(operations.get("getOne"), String.class,
MBeanOperationInfo.INFO, new Class<?>[] { String.class });
assertOperation(operations.get("update"), Void.TYPE,
MBeanOperationInfo.ACTION,
new Class<?>[] { String.class, String.class });
assertOperation(operations.get("deleteOne"), Void.TYPE,
MBeanOperationInfo.ACTION, new Class<?>[] { String.class });
}
catch (Exception ex) {
throw new AssertionError("Failed to retrieve MBeanInfo of FooEndpoint",
ex);
}
});
}
private void assertOperation(MBeanOperationInfo operation, Class<?> returnType,
int impact, Class<?>[] types) {
assertThat(operation.getReturnType()).isEqualTo(returnType.getName());
assertThat(operation.getImpact()).isEqualTo(impact);
MBeanParameterInfo[] signature = operation.getSignature();
assertThat(signature).hasSize(types.length);
for (int i = 0; i < types.length; i++) {
assertThat(signature[i].getType()).isEqualTo(types[0].getName());
}
public void getMBeanInfoShouldReturnMBeanInfo() {
EndpointMBean bean = new EndpointMBean(this.responseMapper, this.endpoint);
MBeanInfo info = bean.getMBeanInfo();
assertThat(info.getDescription()).isEqualTo("MBean operations for endpoint test");
}
@Test
public void invokeReactiveOperation() {
load(ReactiveEndpoint.class, (discoverer) -> {
ObjectName objectName = registerEndpoint(discoverer, "reactive");
try {
Object allResponse = this.server.invoke(objectName, "getInfo",
new Object[0], new String[0]);
assertThat(allResponse).isInstanceOf(String.class);
assertThat(allResponse).isEqualTo("HELLO WORLD");
}
catch (Exception ex) {
throw new AssertionError("Failed to invoke getInfo method", ex);
}
});
public void invokeShouldInvokeJmxOperation()
throws MBeanException, ReflectionException {
EndpointMBean bean = new EndpointMBean(this.responseMapper, this.endpoint);
Object result = bean.invoke("testOperation", NO_PARAMS, NO_SIGNATURE);
assertThat(result).isEqualTo("result");
}
@Test
public void invokeUnknownOperation() {
load(FooEndpoint.class, (discoverer) -> {
ObjectName objectName = registerEndpoint(discoverer, "foo");
try {
this.server.invoke(objectName, "doesNotExist", new Object[0],
new String[0]);
throw new AssertionError(
"Should have failed to invoke unknown operation");
}
catch (ReflectionException ex) {
assertThat(ex.getCause()).isInstanceOf(IllegalArgumentException.class);
assertThat(ex.getCause().getMessage()).contains("doesNotExist", "foo");
}
catch (MBeanException | InstanceNotFoundException ex) {
throw new IllegalStateException(ex);
}
});
public void invokeWhenActionNameIsNotAnOperationShouldThrowException()
throws MBeanException, ReflectionException {
EndpointMBean bean = new EndpointMBean(this.responseMapper, this.endpoint);
this.thrown.expect(ReflectionException.class);
this.thrown.expectCause(instanceOf(IllegalArgumentException.class));
this.thrown.expectMessage("no operation named missingOperation");
bean.invoke("missingOperation", NO_PARAMS, NO_SIGNATURE);
}
@Test
public void dynamicMBeanCannotReadAttribute() {
load(FooEndpoint.class, (discoverer) -> {
ObjectName objectName = registerEndpoint(discoverer, "foo");
try {
this.server.getAttribute(objectName, "foo");
throw new AssertionError("Should have failed to read attribute foo");
}
catch (Exception ex) {
assertThat(ex).isInstanceOf(AttributeNotFoundException.class);
}
});
public void invokeWhenMonoResultShouldBlockOnMono()
throws MBeanException, ReflectionException {
TestExposableJmxEndpoint endpoint = new TestExposableJmxEndpoint(
new TestJmxOperation((arguments) -> Mono.just("monoResult")));
EndpointMBean bean = new EndpointMBean(this.responseMapper, endpoint);
Object result = bean.invoke("testOperation", NO_PARAMS, NO_SIGNATURE);
assertThat(result).isEqualTo("monoResult");
}
@Test
public void dynamicMBeanCannotWriteAttribute() {
load(FooEndpoint.class, (discoverer) -> {
ObjectName objectName = registerEndpoint(discoverer, "foo");
try {
this.server.setAttribute(objectName, new Attribute("foo", "bar"));
throw new AssertionError("Should have failed to write attribute foo");
}
catch (Exception ex) {
assertThat(ex).isInstanceOf(AttributeNotFoundException.class);
}
});
public void invokeShouldCallResponseMapper()
throws MBeanException, ReflectionException {
TestJmxOperationResponseMapper responseMapper = spy(this.responseMapper);
EndpointMBean bean = new EndpointMBean(responseMapper, this.endpoint);
bean.invoke("testOperation", NO_PARAMS, NO_SIGNATURE);
verify(responseMapper).mapResponseType(String.class);
verify(responseMapper).mapResponse("result");
}
@Test
public void dynamicMBeanCannotReadAttributes() {
load(FooEndpoint.class, (discoverer) -> {
ObjectName objectName = registerEndpoint(discoverer, "foo");
try {
AttributeList attributes = this.server.getAttributes(objectName,
new String[] { "foo", "bar" });
assertThat(attributes).isNotNull();
assertThat(attributes).isEmpty();
}
catch (Exception ex) {
throw new AssertionError("Failed to invoke getAttributes", ex);
}
});
public void getAttributeShouldThrowException()
throws AttributeNotFoundException, MBeanException, ReflectionException {
EndpointMBean bean = new EndpointMBean(this.responseMapper, this.endpoint);
this.thrown.expect(AttributeNotFoundException.class);
this.thrown.expectMessage("EndpointMBeans do not support attributes");
bean.getAttribute("test");
}
@Test
public void dynamicMBeanCannotWriteAttributes() {
load(FooEndpoint.class, (discoverer) -> {
ObjectName objectName = registerEndpoint(discoverer, "foo");
try {
AttributeList attributes = new AttributeList();
attributes.add(new Attribute("foo", 1));
attributes.add(new Attribute("bar", 42));
AttributeList attributesSet = this.server.setAttributes(objectName,
attributes);
assertThat(attributesSet).isNotNull();
assertThat(attributesSet).isEmpty();
}
catch (Exception ex) {
throw new AssertionError("Failed to invoke setAttributes", ex);
}
});
public void setAttributeShouldThrowException() throws AttributeNotFoundException,
InvalidAttributeValueException, MBeanException, ReflectionException {
EndpointMBean bean = new EndpointMBean(this.responseMapper, this.endpoint);
this.thrown.expect(AttributeNotFoundException.class);
this.thrown.expectMessage("EndpointMBeans do not support attributes");
bean.setAttribute(new Attribute("test", "test"));
}
@Test
public void invokeWithParameterMappingExceptionMapsToIllegalArgumentException() {
load(FooEndpoint.class, (discoverer) -> {
ObjectName objectName = registerEndpoint(discoverer, "foo");
try {
this.server.invoke(objectName, "getOne", new Object[] { "wrong" },
new String[] { String.class.getName() });
}
catch (Exception ex) {
assertThat(ex.getCause())
.isExactlyInstanceOf(IllegalArgumentException.class);
assertThat(ex.getCause().getMessage()).isEqualTo(
String.format("Failed to map wrong of type " + "%s to type %s",
String.class, FooName.class));
}
});
public void getAttributesShouldReturnEmptyAttributeList() {
EndpointMBean bean = new EndpointMBean(this.responseMapper, this.endpoint);
AttributeList attributes = bean.getAttributes(new String[] { "test" });
assertThat(attributes).isEmpty();
}
@Test
public void invokeWithMissingRequiredParameterExceptionMapsToIllegalArgumentException() {
load(RequiredParametersEndpoint.class, (discoverer) -> {
ObjectName objectName = registerEndpoint(discoverer, "requiredparameters");
try {
this.server.invoke(objectName, "read", new Object[] {},
new String[] { String.class.getName() });
}
catch (Exception ex) {
assertThat(ex.getCause())
.isExactlyInstanceOf(IllegalArgumentException.class);
assertThat(ex.getCause().getMessage())
.isEqualTo("Failed to invoke operation because the following "
+ "required parameters were missing: foo,baz");
}
});
}
@Test
public void invokeWithMissingNullableParameter() {
load(RequiredParametersEndpoint.class, (discoverer) -> {
ObjectName objectName = registerEndpoint(discoverer, "requiredparameters");
try {
this.server.invoke(objectName, "read",
new Object[] { null, "hello", "world" },
new String[] { String.class.getName() });
}
catch (Exception ex) {
throw new AssertionError("Nullable parameter should not be required.");
}
});
}
private ObjectName registerEndpoint(JmxAnnotationEndpointDiscoverer discoverer,
String endpointId) {
Collection<EndpointMBean> mBeans = this.jmxEndpointMBeanFactory
.createMBeans(discoverer.discoverEndpoints());
assertThat(mBeans).hasSize(1);
EndpointMBean endpointMBean = mBeans.iterator().next();
assertThat(endpointMBean.getEndpointId()).isEqualTo(endpointId);
return this.endpointMBeanRegistrar.registerEndpointMBean(endpointMBean);
}
private Map<String, MBeanOperationInfo> mapOperations(MBeanInfo info) {
Map<String, MBeanOperationInfo> operations = new HashMap<>();
for (MBeanOperationInfo mBeanOperationInfo : info.getOperations()) {
operations.put(mBeanOperationInfo.getName(), mBeanOperationInfo);
}
return operations;
}
private void load(Class<?> configuration,
Consumer<JmxAnnotationEndpointDiscoverer> consumer) {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
configuration)) {
ConversionServiceParameterMapper parameterMapper = new ConversionServiceParameterMapper(
DefaultConversionService.getSharedInstance());
JmxAnnotationEndpointDiscoverer discoverer = new JmxAnnotationEndpointDiscoverer(
context, parameterMapper, null, null);
consumer.accept(discoverer);
}
}
@Endpoint(id = "foo")
static class FooEndpoint {
private final Map<FooName, Foo> all = new LinkedHashMap<>();
FooEndpoint() {
this.all.put(FooName.ONE, new Foo("one"));
this.all.put(FooName.TWO, new Foo("two"));
}
@ReadOperation
public Collection<Foo> getAll() {
return this.all.values();
}
@ReadOperation
public Foo getOne(FooName name) {
return this.all.get(name);
}
@WriteOperation
public void update(FooName name, String value) {
this.all.put(name, new Foo(value));
}
@DeleteOperation
public void deleteOne(FooName name) {
this.all.remove(name);
}
}
@Endpoint(id = "requiredparameters")
static class RequiredParametersEndpoint {
@ReadOperation
public String read(@Nullable String bar, String foo, String baz) {
return foo;
}
}
@Endpoint(id = "reactive")
static class ReactiveEndpoint {
@ReadOperation
public Mono<String> getInfo() {
return Mono.defer(() -> Mono.just("Hello World"));
}
}
enum FooName {
ONE, TWO, THREE
}
static class Foo {
private final String name;
Foo(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
@Override
public String toString() {
return this.name;
}
}
private static class TestJmxOperationResponseMapper
implements JmxOperationResponseMapper {
@Override
public Object mapResponse(Object response) {
return (response != null ? response.toString().toUpperCase() : null);
}
@Override
public Class<?> mapResponseType(Class<?> responseType) {
if (responseType == Void.TYPE) {
return Void.TYPE;
}
return String.class;
}
public void setAttributesShouldReturnEmptyAttributeList() {
EndpointMBean bean = new EndpointMBean(this.responseMapper, this.endpoint);
AttributeList sourceAttributes = new AttributeList();
sourceAttributes.add(new Attribute("test", "test"));
AttributeList attributes = bean.setAttributes(sourceAttributes);
assertThat(attributes).isEmpty();
}
}

View File

@@ -0,0 +1,137 @@
/*
* 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.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.springframework.boot.test.json.BasicJsonTester;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link JacksonJmxOperationResponseMapper}
*
* @author Phillip Webb
*/
public class JacksonJmxOperationResponseMapperTests {
private JacksonJmxOperationResponseMapper mapper = new JacksonJmxOperationResponseMapper(
null);
private final BasicJsonTester json = new BasicJsonTester(getClass());
@Test
public void createWhenObjectMapperIsNullShouldUseDefaultObjectMapper() {
JacksonJmxOperationResponseMapper mapper = new JacksonJmxOperationResponseMapper(
null);
Object mapped = mapper.mapResponse(Collections.singleton("test"));
assertThat(this.json.from(mapped.toString())).isEqualToJson("[test]");
}
@Test
public void createWhenObjectMapperIsSpecifiedShouldUseObjectMapper() {
ObjectMapper objectMapper = spy(ObjectMapper.class);
JacksonJmxOperationResponseMapper mapper = new JacksonJmxOperationResponseMapper(
objectMapper);
Set<String> response = Collections.singleton("test");
mapper.mapResponse(response);
verify(objectMapper).convertValue(eq(response), any(JavaType.class));
}
@Test
public void mapResponseTypeWhenCharSequenceShouldReturnString() {
assertThat(this.mapper.mapResponseType(String.class)).isEqualTo(String.class);
assertThat(this.mapper.mapResponseType(StringBuilder.class))
.isEqualTo(String.class);
}
@Test
public void mapResponseTypeWhenArrayShouldReturnList() {
assertThat(this.mapper.mapResponseType(String[].class)).isEqualTo(List.class);
assertThat(this.mapper.mapResponseType(Object[].class)).isEqualTo(List.class);
}
@Test
public void mapResponseTypeWhenCollectionShouldReturnList() {
assertThat(this.mapper.mapResponseType(Collection.class)).isEqualTo(List.class);
assertThat(this.mapper.mapResponseType(Set.class)).isEqualTo(List.class);
assertThat(this.mapper.mapResponseType(List.class)).isEqualTo(List.class);
}
@Test
public void mapResponseTypeWhenOtherShouldReturnMap() {
assertThat(this.mapper.mapResponseType(ExampleBean.class)).isEqualTo(Map.class);
}
@Test
public void mapResponseWhenNullShouldReturnNull() {
assertThat(this.mapper.mapResponse(null)).isNull();
}
@Test
public void mapResponseWhenCharSequenceShouldReturnString() {
assertThat(this.mapper.mapResponse(new StringBuilder("test"))).isEqualTo("test");
}
@Test
public void mapResponseWhenArrayShouldReturnJsonArray() {
Object mapped = this.mapper.mapResponse(new int[] { 1, 2, 3 });
assertThat(this.json.from(mapped.toString())).isEqualToJson("[1,2,3]");
}
@Test
public void mapResponseWhenCollectionShouldReturnJsonArray() {
Object mapped = this.mapper.mapResponse(Arrays.asList("a", "b", "c"));
assertThat(this.json.from(mapped.toString())).isEqualToJson("[a,b,c]");
}
@Test
public void mapResponseWhenOtherShouldReturnMap() {
ExampleBean bean = new ExampleBean();
bean.setName("boot");
Object mapped = this.mapper.mapResponse(bean);
assertThat(this.json.from(mapped.toString())).isEqualToJson("{'name':'boot'}");
}
public static class ExampleBean {
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
}

View File

@@ -0,0 +1,199 @@
/*
* 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.ArrayList;
import java.util.List;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.jmx.JmxException;
import org.springframework.jmx.export.MBeanExportException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link JmxEndpointExporter}.
*
* @author Stephane Nicoll
* @author Phillip Webb
*/
public class JmxEndpointExporterTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Mock
private MBeanServer mBeanServer;
private EndpointObjectNameFactory objectNameFactory = spy(
new TestEndpointObjectNameFactory());
private JmxOperationResponseMapper responseMapper = new TestJmxOperationResponseMapper();
private List<ExposableJmxEndpoint> endpoints = new ArrayList<>();
@Captor
private ArgumentCaptor<Object> objectCaptor;
@Captor
private ArgumentCaptor<ObjectName> objectNameCaptor;
private JmxEndpointExporter exporter;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.exporter = new JmxEndpointExporter(this.mBeanServer, this.objectNameFactory,
this.responseMapper, this.endpoints);
}
@Test
public void createWhenMBeanServerIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("MBeanServer must not be null");
new JmxEndpointExporter(null, this.objectNameFactory, this.responseMapper,
this.endpoints);
}
@Test
public void createWhenObjectNameFactoryIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("ObjectNameFactory must not be null");
new JmxEndpointExporter(this.mBeanServer, null, this.responseMapper,
this.endpoints);
}
@Test
public void createWhenResponseMapperIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("ResponseMapper must not be null");
new JmxEndpointExporter(this.mBeanServer, this.objectNameFactory, null,
this.endpoints);
}
@Test
public void createWhenEndpointsIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Endpoints must not be null");
new JmxEndpointExporter(this.mBeanServer, this.objectNameFactory,
this.responseMapper, null);
}
@Test
public void afterPropertiesSetShouldRegisterMBeans() throws Exception {
this.endpoints.add(new TestExposableJmxEndpoint(new TestJmxOperation()));
this.exporter.afterPropertiesSet();
verify(this.mBeanServer).registerMBean(this.objectCaptor.capture(),
this.objectNameCaptor.capture());
assertThat(this.objectCaptor.getValue()).isInstanceOf(EndpointMBean.class);
assertThat(this.objectNameCaptor.getValue().getKeyProperty("name"))
.isEqualTo("test");
}
@Test
public void registerShouldUseObjectNameFactory() throws Exception {
this.endpoints.add(new TestExposableJmxEndpoint(new TestJmxOperation()));
this.exporter.afterPropertiesSet();
verify(this.objectNameFactory).getObjectName(any(ExposableJmxEndpoint.class));
}
@Test
public void registerWhenObjectNameIsMalformedShouldThrowException() throws Exception {
given(this.objectNameFactory.getObjectName(any(ExposableJmxEndpoint.class)))
.willThrow(MalformedObjectNameException.class);
this.endpoints.add(new TestExposableJmxEndpoint(new TestJmxOperation()));
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Invalid ObjectName for endpoint 'test'");
this.exporter.afterPropertiesSet();
}
@Test
public void registerWhenRegistrationFailsShouldThrowException() throws Exception {
given(this.mBeanServer.registerMBean(any(), any(ObjectName.class)))
.willThrow(new MBeanRegistrationException(new RuntimeException()));
this.endpoints.add(new TestExposableJmxEndpoint(new TestJmxOperation()));
this.thrown.expect(MBeanExportException.class);
this.thrown.expectMessage("Failed to register MBean for endpoint 'test");
this.exporter.afterPropertiesSet();
}
@Test
public void destroyShouldUnregisterMBeans() throws Exception {
this.endpoints.add(new TestExposableJmxEndpoint(new TestJmxOperation()));
this.exporter.afterPropertiesSet();
this.exporter.destroy();
verify(this.mBeanServer).unregisterMBean(this.objectNameCaptor.capture());
assertThat(this.objectNameCaptor.getValue().getKeyProperty("name"))
.isEqualTo("test");
}
@Test
public void unregisterWhenInstanceNotFoundShouldContinue() throws Exception {
this.endpoints.add(new TestExposableJmxEndpoint(new TestJmxOperation()));
this.exporter.afterPropertiesSet();
willThrow(InstanceNotFoundException.class).given(this.mBeanServer)
.unregisterMBean(any(ObjectName.class));
this.exporter.destroy();
}
@Test
public void unregisterWhenUnregisterThrowsExceptionShouldThrowException()
throws Exception {
this.endpoints.add(new TestExposableJmxEndpoint(new TestJmxOperation()));
this.exporter.afterPropertiesSet();
willThrow(new MBeanRegistrationException(new RuntimeException()))
.given(this.mBeanServer).unregisterMBean(any(ObjectName.class));
this.thrown.expect(JmxException.class);
this.thrown.expectMessage("Failed to unregister MBean with ObjectName 'boot");
this.exporter.destroy();
}
/**
* Test {@link EndpointObjectNameFactory}.
*/
private static class TestEndpointObjectNameFactory
implements EndpointObjectNameFactory {
@Override
public ObjectName getObjectName(ExposableJmxEndpoint endpoint)
throws MalformedObjectNameException {
return (endpoint == null ? null
: new ObjectName("boot:type=Endpoint,name=" + endpoint.getId()));
}
}
}

View File

@@ -0,0 +1,128 @@
/*
* 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.ArrayList;
import java.util.List;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import org.junit.Test;
import org.springframework.boot.actuate.endpoint.OperationType;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link MBeanInfoFactory}.
*
* @author Stephane Nicoll
* @author Phillip Webb
*/
public class MBeanInfoFactoryTests {
private MBeanInfoFactory factory = new MBeanInfoFactory(
new TestJmxOperationResponseMapper());
@Test
public void getMBeanInfoShouldReturnMBeanInfo() {
MBeanInfo info = this.factory
.getMBeanInfo(new TestExposableJmxEndpoint(new TestJmxOperation()));
assertThat(info).isNotNull();
assertThat(info.getClassName()).isEqualTo(EndpointMBean.class.getName());
assertThat(info.getDescription()).isEqualTo("MBean operations for endpoint test");
assertThat(info.getAttributes()).isEmpty();
assertThat(info.getNotifications()).isEmpty();
assertThat(info.getConstructors()).isEmpty();
assertThat(info.getOperations()).hasSize(1);
MBeanOperationInfo operationInfo = info.getOperations()[0];
assertThat(operationInfo.getName()).isEqualTo("testOperation");
assertThat(operationInfo.getReturnType()).isEqualTo(String.class.getName());
assertThat(operationInfo.getImpact()).isEqualTo(MBeanOperationInfo.INFO);
assertThat(operationInfo.getSignature()).hasSize(0);
}
@Test
public void getMBeanInfoWhenReadOperationShouldHaveInfoImpact() {
MBeanInfo info = this.factory.getMBeanInfo(
new TestExposableJmxEndpoint(new TestJmxOperation(OperationType.READ)));
assertThat(info.getOperations()[0].getImpact())
.isEqualTo(MBeanOperationInfo.INFO);
}
@Test
public void getMBeanInfoWhenWriteOperationShouldHaveActionImpact() {
MBeanInfo info = this.factory.getMBeanInfo(
new TestExposableJmxEndpoint(new TestJmxOperation(OperationType.WRITE)));
assertThat(info.getOperations()[0].getImpact())
.isEqualTo(MBeanOperationInfo.ACTION);
}
@Test
public void getMBeanInfoWhenDeleteOperationShouldHaveActionImpact() {
MBeanInfo info = this.factory.getMBeanInfo(
new TestExposableJmxEndpoint(new TestJmxOperation(OperationType.DELETE)));
assertThat(info.getOperations()[0].getImpact())
.isEqualTo(MBeanOperationInfo.ACTION);
}
@Test
@SuppressWarnings({ "unchecked", "rawtypes" })
public void getMBeanInfoShouldUseJmxOperationResponseMapper() {
JmxOperationResponseMapper mapper = mock(JmxOperationResponseMapper.class);
given(mapper.mapResponseType(String.class)).willReturn((Class) Integer.class);
MBeanInfoFactory factory = new MBeanInfoFactory(mapper);
MBeanInfo info = factory
.getMBeanInfo(new TestExposableJmxEndpoint(new TestJmxOperation()));
MBeanOperationInfo operationInfo = info.getOperations()[0];
assertThat(operationInfo.getReturnType()).isEqualTo(Integer.class.getName());
}
@Test
public void getMBeanShouldMapOperationParameters() {
List<JmxOperationParameter> parameters = new ArrayList<>();
parameters.add(mockParameter("one", String.class, "myone"));
parameters.add(mockParameter("two", Object.class, null));
TestJmxOperation operation = new TestJmxOperation(parameters);
MBeanInfo info = this.factory
.getMBeanInfo(new TestExposableJmxEndpoint(operation));
MBeanOperationInfo operationInfo = info.getOperations()[0];
MBeanParameterInfo[] signature = operationInfo.getSignature();
assertThat(signature).hasSize(2);
assertThat(signature[0].getName()).isEqualTo("one");
assertThat(signature[0].getType()).isEqualTo(String.class.getName());
assertThat(signature[0].getDescription()).isEqualTo("myone");
assertThat(signature[1].getName()).isEqualTo("two");
assertThat(signature[1].getType()).isEqualTo(Object.class.getName());
assertThat(signature[1].getDescription()).isNull();
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private JmxOperationParameter mockParameter(String name, Class<?> type,
String description) {
JmxOperationParameter parameter = mock(JmxOperationParameter.class);
given(parameter.getName()).willReturn(name);
given(parameter.getType()).willReturn((Class) type);
given(parameter.getDescription()).willReturn(description);
return parameter;
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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.Arrays;
import java.util.Collection;
/**
* Test {@link ExposableJmxEndpoint} implementation.
*
* @author Phillip Webb
*/
public class TestExposableJmxEndpoint implements ExposableJmxEndpoint {
private final Collection<JmxOperation> operations;
public TestExposableJmxEndpoint(JmxOperation... operations) {
this(Arrays.asList(operations));
}
public TestExposableJmxEndpoint(Collection<JmxOperation> operations) {
this.operations = operations;
}
@Override
public String getId() {
return "test";
}
@Override
public boolean isEnableByDefault() {
return true;
}
@Override
public Collection<JmxOperation> getOperations() {
return this.operations;
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.springframework.boot.actuate.endpoint.OperationType;
/**
* Test {@link JmxOperation} implementation.
*
* @author Phillip Webb
*/
public class TestJmxOperation implements JmxOperation {
private final OperationType operationType;
private final Function<Map<String, Object>, Object> invoke;
private final List<JmxOperationParameter> parameters;
public TestJmxOperation() {
this.operationType = OperationType.READ;
this.invoke = null;
this.parameters = Collections.emptyList();
}
public TestJmxOperation(OperationType operationType) {
this.operationType = operationType;
this.invoke = null;
this.parameters = Collections.emptyList();
}
public TestJmxOperation(Function<Map<String, Object>, Object> invoke) {
this.operationType = OperationType.READ;
this.invoke = invoke;
this.parameters = Collections.emptyList();
}
public TestJmxOperation(List<JmxOperationParameter> parameters) {
this.operationType = OperationType.READ;
this.invoke = null;
this.parameters = parameters;
}
@Override
public OperationType getType() {
return this.operationType;
}
@Override
public Object invoke(Map<String, Object> arguments) {
return (this.invoke == null ? "result" : this.invoke.apply(arguments));
}
@Override
public String getName() {
return "testOperation";
}
@Override
public Class<?> getOutputType() {
return String.class;
}
@Override
public String getDescription() {
return "Test JMX operation";
}
@Override
public List<JmxOperationParameter> getParameters() {
return this.parameters;
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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;
/**
* Test {@link JmxOperationResponseMapper} implementation.
*
* @author Stephane Nicoll
*/
class TestJmxOperationResponseMapper implements JmxOperationResponseMapper {
@Override
public Object mapResponse(Object response) {
return response;
}
@Override
public Class<?> mapResponseType(Class<?> responseType) {
return responseType;
}
}

View File

@@ -0,0 +1,168 @@
/*
* 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.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
import org.springframework.boot.actuate.endpoint.jmx.JmxOperationParameter;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedOperationParameter;
import org.springframework.jmx.export.annotation.ManagedOperationParameters;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link DiscoveredJmxOperation}.
*
* @author Phillip Webb
*/
public class DiscoveredJmxOperationTests {
@Test
public void getNameShouldReturnMethodName() {
DiscoveredJmxOperation operation = getOperation("getEnum");
assertThat(operation.getName()).isEqualTo("getEnum");
}
@Test
public void getOutputTypeShouldReturnJmxType() {
assertThat(getOperation("getEnum").getOutputType()).isEqualTo(String.class);
assertThat(getOperation("getDate").getOutputType()).isEqualTo(String.class);
assertThat(getOperation("getInstant").getOutputType()).isEqualTo(String.class);
assertThat(getOperation("getInteger").getOutputType()).isEqualTo(Integer.class);
assertThat(getOperation("getVoid").getOutputType()).isEqualTo(void.class);
assertThat(getOperation("getApplicationContext").getOutputType())
.isEqualTo(Object.class);
}
@Test
public void getDescriptionWhenHasManagedOperationDescriptionShouldUseValueFromAnnotation() {
DiscoveredJmxOperation operation = getOperation(
"withManagedOperationDescription");
assertThat(operation.getDescription()).isEqualTo("fromannotation");
}
@Test
public void getDescriptionWhenHasNoManagedOperationShouldGenerateDescription() {
DiscoveredJmxOperation operation = getOperation("getEnum");
assertThat(operation.getDescription())
.isEqualTo("Invoke getEnum for endpoint test");
}
@Test
public void getParametersWhenHasNoParametersShouldReturnEmptyList() {
DiscoveredJmxOperation operation = getOperation("getEnum");
assertThat(operation.getParameters()).isEmpty();
}
@Test
public void getParametersShouldReturnJmxTypes() {
DiscoveredJmxOperation operation = getOperation("params");
List<JmxOperationParameter> parameters = operation.getParameters();
assertThat(parameters.get(0).getType()).isEqualTo(String.class);
assertThat(parameters.get(1).getType()).isEqualTo(String.class);
assertThat(parameters.get(2).getType()).isEqualTo(String.class);
assertThat(parameters.get(3).getType()).isEqualTo(Integer.class);
assertThat(parameters.get(4).getType()).isEqualTo(Object.class);
}
@Test
public void getParametersWhenHasManagedOperationParameterShouldUseValuesFromAnnotation() {
DiscoveredJmxOperation operation = getOperation("withManagedOperationParameters");
List<JmxOperationParameter> parameters = operation.getParameters();
assertThat(parameters.get(0).getName()).isEqualTo("a1");
assertThat(parameters.get(1).getName()).isEqualTo("a2");
assertThat(parameters.get(0).getDescription()).isEqualTo("d1");
assertThat(parameters.get(1).getDescription()).isEqualTo("d2");
}
@Test
public void getParametersWhenHasNoManagedOperationParameterShouldDeducedValuesName() {
DiscoveredJmxOperation operation = getOperation("params");
List<JmxOperationParameter> parameters = operation.getParameters();
assertThat(parameters.get(0).getName()).isEqualTo("enumParam");
assertThat(parameters.get(1).getName()).isEqualTo("dateParam");
assertThat(parameters.get(2).getName()).isEqualTo("instantParam");
assertThat(parameters.get(3).getName()).isEqualTo("integerParam");
assertThat(parameters.get(4).getName()).isEqualTo("applicationContextParam");
assertThat(parameters.get(0).getDescription()).isNull();
assertThat(parameters.get(1).getDescription()).isNull();
assertThat(parameters.get(2).getDescription()).isNull();
assertThat(parameters.get(3).getDescription()).isNull();
assertThat(parameters.get(4).getDescription()).isNull();
}
private DiscoveredJmxOperation getOperation(String methodName) {
Method method = findMethod(methodName);
AnnotationAttributes annotationAttributes = new AnnotationAttributes();
annotationAttributes.put("produces", "application/xml");
DiscoveredOperationMethod operationMethod = new DiscoveredOperationMethod(method,
OperationType.READ, annotationAttributes);
DiscoveredJmxOperation operation = new DiscoveredJmxOperation("test",
operationMethod, mock(OperationInvoker.class));
return operation;
}
private Method findMethod(String methodName) {
Map<String, Method> methods = new HashMap<>();
ReflectionUtils.doWithMethods(Example.class,
(method) -> methods.put(method.getName(), method));
return methods.get(methodName);
}
interface Example {
OperationType getEnum();
Date getDate();
Instant getInstant();
Integer getInteger();
void getVoid();
ApplicationContext getApplicationContext();
Object params(OperationType enumParam, Date dateParam, Instant instantParam,
Integer integerParam, ApplicationContext applicationContextParam);
@ManagedOperation(description = "fromannotation")
Object withManagedOperationDescription();
@ManagedOperationParameters({
@ManagedOperationParameter(name = "a1", description = "d1"),
@ManagedOperationParameter(name = "a2", description = "d2") })
Object withManagedOperationParameters(Object one, Object two);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,17 +28,16 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.boot.actuate.endpoint.cache.CachingOperationInvoker;
import org.springframework.boot.actuate.endpoint.cache.CachingOperationInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper;
import org.springframework.boot.actuate.endpoint.jmx.JmxEndpointOperationParameterInfo;
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
import org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvoker;
import org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.jmx.ExposableJmxEndpoint;
import org.springframework.boot.actuate.endpoint.jmx.JmxOperation;
import org.springframework.boot.actuate.endpoint.reflect.ReflectiveOperationInvoker;
import org.springframework.boot.actuate.endpoint.jmx.JmxOperationParameter;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -47,30 +46,31 @@ import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedOperationParameter;
import org.springframework.jmx.export.annotation.ManagedOperationParameters;
import org.springframework.util.ReflectionUtils;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link JmxAnnotationEndpointDiscoverer}.
* Tests for {@link JmxEndpointDiscoverer}.
*
* @author Stephane Nicoll
* @author Phillip Webb
*/
public class JmxAnnotationEndpointDiscovererTests {
public class JmxEndpointDiscovererTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Test
public void discoveryWorksWhenThereAreNoEndpoints() {
public void getEndpointsWhenNoEndpointBeansShouldReturnEmptyCollection() {
load(EmptyConfiguration.class,
(discoverer) -> assertThat(discoverer.discoverEndpoints()).isEmpty());
(discoverer) -> assertThat(discoverer.getEndpoints()).isEmpty());
}
@Test
public void standardEndpointIsDiscovered() {
public void getEndpointsShouldDiscoverStandardEndpoints() {
load(TestEndpoint.class, (discoverer) -> {
Map<String, EndpointInfo<JmxOperation>> endpoints = discover(discoverer);
Map<String, ExposableJmxEndpoint> endpoints = discover(discoverer);
assertThat(endpoints).containsOnlyKeys("test");
Map<String, JmxOperation> operationByName = mapOperations(
endpoints.get("test").getOperations());
@@ -81,8 +81,6 @@ public class JmxAnnotationEndpointDiscovererTests {
.isEqualTo("Invoke getAll for endpoint test");
assertThat(getAll.getOutputType()).isEqualTo(Object.class);
assertThat(getAll.getParameters()).isEmpty();
assertThat(getAll.getInvoker())
.isInstanceOf(ReflectiveOperationInvoker.class);
JmxOperation getSomething = operationByName.get("getSomething");
assertThat(getSomething.getDescription())
.isEqualTo("Invoke getSomething for endpoint test");
@@ -103,42 +101,41 @@ public class JmxAnnotationEndpointDiscovererTests {
assertThat(deleteSomething.getParameters()).hasSize(1);
hasDefaultParameter(deleteSomething, 0, String.class);
});
}
@Test
public void onlyJmxEndpointsAreDiscovered() {
public void getEndpointsWhenHasFilteredEndpointShouldOnlyDiscoverJmxEndpoints() {
load(MultipleEndpointsConfiguration.class, (discoverer) -> {
Map<String, EndpointInfo<JmxOperation>> endpoints = discover(discoverer);
Map<String, ExposableJmxEndpoint> endpoints = discover(discoverer);
assertThat(endpoints).containsOnlyKeys("test", "jmx");
});
}
@Test
public void jmxExtensionMustHaveEndpoint() {
public void getEndpointsWhenJmxExtensionIsMissingEndpointShouldThrowException() {
load(TestJmxEndpointExtension.class, (discoverer) -> {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Invalid extension");
this.thrown.expectMessage(TestJmxEndpointExtension.class.getName());
this.thrown.expectMessage("no endpoint found");
this.thrown.expectMessage(TestEndpoint.class.getName());
discoverer.discoverEndpoints();
this.thrown.expectMessage(
"Invalid extension 'jmxEndpointDiscovererTests.TestJmxEndpointExtension': "
+ "no endpoint found with type '"
+ TestEndpoint.class.getName() + "'");
discoverer.getEndpoints();
});
}
@Test
public void jmxEndpointOverridesStandardEndpoint() {
public void getEndpointsWhenHasJmxExtensionShouldOverrideStandardEndpoint() {
load(OverriddenOperationJmxEndpointConfiguration.class, (discoverer) -> {
Map<String, EndpointInfo<JmxOperation>> endpoints = discover(discoverer);
Map<String, ExposableJmxEndpoint> endpoints = discover(discoverer);
assertThat(endpoints).containsOnlyKeys("test");
assertJmxTestEndpoint(endpoints.get("test"));
});
}
@Test
public void jmxEndpointAddsExtraOperation() {
public void getEndpointsWhenHasJmxExtensionWithNewOperationAddsExtraOperation() {
load(AdditionalOperationJmxEndpointConfiguration.class, (discoverer) -> {
Map<String, EndpointInfo<JmxOperation>> endpoints = discover(discoverer);
Map<String, ExposableJmxEndpoint> endpoints = discover(discoverer);
assertThat(endpoints).containsOnlyKeys("test");
Map<String, JmxOperation> operationByName = mapOperations(
endpoints.get("test").getOperations());
@@ -152,131 +149,120 @@ public class JmxAnnotationEndpointDiscovererTests {
}
@Test
public void endpointMainReadOperationIsCachedWithMatchingId() {
public void getEndpointsWhenHasCacheWithTtlShouldCacheReadOperationWithTtlValue() {
load(TestEndpoint.class, (id) -> 500L, (discoverer) -> {
Map<String, EndpointInfo<JmxOperation>> endpoints = discover(discoverer);
Map<String, ExposableJmxEndpoint> endpoints = discover(discoverer);
assertThat(endpoints).containsOnlyKeys("test");
Map<String, JmxOperation> operationByName = mapOperations(
endpoints.get("test").getOperations());
assertThat(operationByName).containsOnlyKeys("getAll", "getSomething",
"update", "deleteSomething");
JmxOperation getAll = operationByName.get("getAll");
assertThat(getAll.getInvoker()).isInstanceOf(CachingOperationInvoker.class);
assertThat(((CachingOperationInvoker) getAll.getInvoker()).getTimeToLive())
assertThat(getInvoker(getAll)).isInstanceOf(CachingOperationInvoker.class);
assertThat(((CachingOperationInvoker) getInvoker(getAll)).getTimeToLive())
.isEqualTo(500);
});
}
@Test
public void extraReadOperationsAreCached() {
public void getEndpointsShouldCacheReadOperations() {
load(AdditionalOperationJmxEndpointConfiguration.class, (id) -> 500L,
(discoverer) -> {
Map<String, EndpointInfo<JmxOperation>> endpoints = discover(
discoverer);
Map<String, ExposableJmxEndpoint> endpoints = discover(discoverer);
assertThat(endpoints).containsOnlyKeys("test");
Map<String, JmxOperation> operationByName = mapOperations(
endpoints.get("test").getOperations());
assertThat(operationByName).containsOnlyKeys("getAll", "getSomething",
"update", "deleteSomething", "getAnother");
JmxOperation getAll = operationByName.get("getAll");
assertThat(getAll.getInvoker())
assertThat(getInvoker(getAll))
.isInstanceOf(CachingOperationInvoker.class);
assertThat(((CachingOperationInvoker) getAll.getInvoker())
assertThat(((CachingOperationInvoker) getInvoker(getAll))
.getTimeToLive()).isEqualTo(500);
JmxOperation getAnother = operationByName.get("getAnother");
assertThat(getAnother.getInvoker())
assertThat(getInvoker(getAnother))
.isInstanceOf(CachingOperationInvoker.class);
assertThat(((CachingOperationInvoker) getAnother.getInvoker())
assertThat(((CachingOperationInvoker) getInvoker(getAnother))
.getTimeToLive()).isEqualTo(500);
});
}
@Test
public void discoveryFailsWhenTwoExtensionsHaveTheSameEndpointType() {
public void getEndpointsWhenTwoExtensionsHaveTheSameEndpointTypeShouldThrowException() {
load(ClashingJmxEndpointConfiguration.class, (discoverer) -> {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Found two extensions for the same endpoint");
this.thrown.expectMessage(TestEndpoint.class.getName());
this.thrown.expectMessage(TestJmxEndpointExtension.class.getName());
discoverer.discoverEndpoints();
this.thrown.expectMessage("Found multiple extensions for the endpoint bean "
+ "testEndpoint (testExtensionOne, testExtensionTwo)");
discoverer.getEndpoints();
});
}
@Test
public void discoveryFailsWhenTwoStandardEndpointsHaveTheSameId() {
public void getEndpointsWhenTwoStandardEndpointsHaveTheSameIdShouldThrowException() {
load(ClashingStandardEndpointConfiguration.class, (discoverer) -> {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Found two endpoints with the id 'test': ");
discoverer.discoverEndpoints();
discoverer.getEndpoints();
});
}
@Test
public void discoveryFailsWhenEndpointHasTwoOperationsWithTheSameName() {
public void getEndpointsWhenWhenEndpointHasTwoOperationsWithTheSameNameShouldThrowException() {
load(ClashingOperationsEndpoint.class, (discoverer) -> {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Found multiple JMX operations with the same name");
this.thrown.expectMessage("getAll");
this.thrown.expectMessage(ReflectionUtils
.findMethod(ClashingOperationsEndpoint.class, "getAll").toString());
this.thrown.expectMessage(ReflectionUtils
.findMethod(ClashingOperationsEndpoint.class, "getAll", String.class)
.toString());
discoverer.discoverEndpoints();
this.thrown.expectMessage("Unable to map duplicate endpoint operations: "
+ "[MBean call 'getAll'] to jmxEndpointDiscovererTests.ClashingOperationsEndpoint");
discoverer.getEndpoints();
});
}
@Test
public void discoveryFailsWhenExtensionHasTwoOperationsWithTheSameName() {
public void getEndpointsWhenWhenExtensionHasTwoOperationsWithTheSameNameShouldThrowException() {
load(AdditionalClashingOperationsConfiguration.class, (discoverer) -> {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Found multiple JMX operations with the same name");
this.thrown.expectMessage("getAll");
this.thrown.expectMessage(ReflectionUtils
.findMethod(ClashingOperationsJmxEndpointExtension.class, "getAll")
.toString());
this.thrown.expectMessage(ReflectionUtils
.findMethod(ClashingOperationsJmxEndpointExtension.class, "getAll",
String.class)
.toString());
discoverer.discoverEndpoints();
this.thrown.expectMessage("Unable to map duplicate endpoint operations: "
+ "[MBean call 'getAll'] to testEndpoint (clashingOperationsJmxEndpointExtension)");
discoverer.getEndpoints();
});
}
@Test
public void discoveryFailsWhenExtensionIsNotCompatibleWithTheEndpointType() {
public void getEndpointsWhenExtensionIsNotCompatibleWithTheEndpointTypeShouldThrowException() {
load(InvalidJmxExtensionConfiguration.class, (discoverer) -> {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Invalid extension");
this.thrown.expectMessage(NonJmxJmxEndpointExtension.class.getName());
this.thrown.expectMessage(NonJmxEndpoint.class.getName());
discoverer.discoverEndpoints();
this.thrown.expectMessage("Endpoint bean 'nonJmxEndpoint' cannot support the "
+ "extension bean 'nonJmxJmxEndpointExtension'");
discoverer.getEndpoints();
});
}
private void assertJmxTestEndpoint(EndpointInfo<JmxOperation> endpoint) {
Map<String, JmxOperation> operationByName = mapOperations(
private Object getInvoker(JmxOperation operation) {
return ReflectionTestUtils.getField(operation, "invoker");
}
private void assertJmxTestEndpoint(ExposableJmxEndpoint endpoint) {
Map<String, JmxOperation> operationsByName = mapOperations(
endpoint.getOperations());
assertThat(operationByName).containsOnlyKeys("getAll", "getSomething", "update",
assertThat(operationsByName).containsOnlyKeys("getAll", "getSomething", "update",
"deleteSomething");
JmxOperation getAll = operationByName.get("getAll");
JmxOperation getAll = operationsByName.get("getAll");
assertThat(getAll.getDescription()).isEqualTo("Get all the things");
assertThat(getAll.getOutputType()).isEqualTo(Object.class);
assertThat(getAll.getParameters()).isEmpty();
JmxOperation getSomething = operationByName.get("getSomething");
JmxOperation getSomething = operationsByName.get("getSomething");
assertThat(getSomething.getDescription())
.isEqualTo("Get something based on a timeUnit");
assertThat(getSomething.getOutputType()).isEqualTo(String.class);
assertThat(getSomething.getParameters()).hasSize(1);
hasDocumentedParameter(getSomething, 0, "unitMs", Long.class,
"Number of milliseconds");
JmxOperation update = operationByName.get("update");
JmxOperation update = operationsByName.get("update");
assertThat(update.getDescription()).isEqualTo("Update something based on bar");
assertThat(update.getOutputType()).isEqualTo(Void.TYPE);
assertThat(update.getParameters()).hasSize(2);
hasDocumentedParameter(update, 0, "foo", String.class, "Foo identifier");
hasDocumentedParameter(update, 1, "bar", String.class, "Bar value");
JmxOperation deleteSomething = operationByName.get("deleteSomething");
JmxOperation deleteSomething = operationsByName.get("deleteSomething");
assertThat(deleteSomething.getDescription())
.isEqualTo("Delete something based on a timeUnit");
assertThat(deleteSomething.getOutputType()).isEqualTo(Void.TYPE);
@@ -285,58 +271,172 @@ public class JmxAnnotationEndpointDiscovererTests {
"Number of milliseconds");
}
private void hasDefaultParameter(JmxOperation operation, int index, Class<?> type) {
assertThat(index).isLessThan(operation.getParameters().size());
JmxEndpointOperationParameterInfo parameter = operation.getParameters()
.get(index);
assertThat(parameter.getType()).isEqualTo(type);
assertThat(parameter.getDescription()).isNull();
}
private void hasDocumentedParameter(JmxOperation operation, int index, String name,
Class<?> type, String description) {
assertThat(index).isLessThan(operation.getParameters().size());
JmxEndpointOperationParameterInfo parameter = operation.getParameters()
.get(index);
JmxOperationParameter parameter = operation.getParameters().get(index);
assertThat(parameter.getName()).isEqualTo(name);
assertThat(parameter.getType()).isEqualTo(type);
assertThat(parameter.getDescription()).isEqualTo(description);
}
private Map<String, EndpointInfo<JmxOperation>> discover(
JmxAnnotationEndpointDiscoverer discoverer) {
Map<String, EndpointInfo<JmxOperation>> endpointsById = new HashMap<>();
discoverer.discoverEndpoints()
.forEach((endpoint) -> endpointsById.put(endpoint.getId(), endpoint));
return endpointsById;
// FIXME rename
private void hasDefaultParameter(JmxOperation operation, int index, Class<?> type) {
JmxOperationParameter parameter = operation.getParameters().get(index);
assertThat(parameter.getType()).isEqualTo(type);
}
private Map<String, ExposableJmxEndpoint> discover(JmxEndpointDiscoverer discoverer) {
Map<String, ExposableJmxEndpoint> byId = new HashMap<>();
discoverer.getEndpoints()
.forEach((endpoint) -> byId.put(endpoint.getId(), endpoint));
return byId;
}
private Map<String, JmxOperation> mapOperations(Collection<JmxOperation> operations) {
Map<String, JmxOperation> operationByName = new HashMap<>();
operations.forEach((operation) -> operationByName
.put(operation.getOperationName(), operation));
return operationByName;
Map<String, JmxOperation> byName = new HashMap<>();
operations.forEach((operation) -> byName.put(operation.getName(), operation));
return byName;
}
private void load(Class<?> configuration,
Consumer<JmxAnnotationEndpointDiscoverer> consumer) {
private void load(Class<?> configuration, Consumer<JmxEndpointDiscoverer> consumer) {
load(configuration, (id) -> null, consumer);
}
private void load(Class<?> configuration, Function<String, Long> timeToLive,
Consumer<JmxAnnotationEndpointDiscoverer> consumer) {
Consumer<JmxEndpointDiscoverer> consumer) {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
configuration)) {
ConversionServiceParameterMapper parameterMapper = new ConversionServiceParameterMapper(
ConversionServiceParameterValueMapper parameterMapper = new ConversionServiceParameterValueMapper(
DefaultConversionService.getSharedInstance());
JmxAnnotationEndpointDiscoverer discoverer = new JmxAnnotationEndpointDiscoverer(
context, parameterMapper,
JmxEndpointDiscoverer discoverer = new JmxEndpointDiscoverer(context,
parameterMapper,
Collections.singleton(new CachingOperationInvokerAdvisor(timeToLive)),
null);
Collections.emptyList());
consumer.accept(discoverer);
}
}
@Configuration
static class EmptyConfiguration {
}
@Configuration
static class MultipleEndpointsConfiguration {
@Bean
public TestEndpoint testEndpoint() {
return new TestEndpoint();
}
@Bean
public TestJmxEndpoint testJmxEndpoint() {
return new TestJmxEndpoint();
}
@Bean
public NonJmxEndpoint nonJmxEndpoint() {
return new NonJmxEndpoint();
}
}
@Configuration
static class OverriddenOperationJmxEndpointConfiguration {
@Bean
public TestEndpoint testEndpoint() {
return new TestEndpoint();
}
@Bean
public TestJmxEndpointExtension testJmxEndpointExtension() {
return new TestJmxEndpointExtension();
}
}
@Configuration
static class AdditionalOperationJmxEndpointConfiguration {
@Bean
public TestEndpoint testEndpoint() {
return new TestEndpoint();
}
@Bean
public AdditionalOperationJmxEndpointExtension additionalOperationJmxEndpointExtension() {
return new AdditionalOperationJmxEndpointExtension();
}
}
@Configuration
static class AdditionalClashingOperationsConfiguration {
@Bean
public TestEndpoint testEndpoint() {
return new TestEndpoint();
}
@Bean
public ClashingOperationsJmxEndpointExtension clashingOperationsJmxEndpointExtension() {
return new ClashingOperationsJmxEndpointExtension();
}
}
@Configuration
static class ClashingJmxEndpointConfiguration {
@Bean
public TestEndpoint testEndpoint() {
return new TestEndpoint();
}
@Bean
public TestJmxEndpointExtension testExtensionOne() {
return new TestJmxEndpointExtension();
}
@Bean
public TestJmxEndpointExtension testExtensionTwo() {
return new TestJmxEndpointExtension();
}
}
@Configuration
static class ClashingStandardEndpointConfiguration {
@Bean
public TestEndpoint testEndpointTwo() {
return new TestEndpoint();
}
@Bean
public TestEndpoint testEndpointOne() {
return new TestEndpoint();
}
}
@Configuration
static class InvalidJmxExtensionConfiguration {
@Bean
public NonJmxEndpoint nonJmxEndpoint() {
return new NonJmxEndpoint();
}
@Bean
public NonJmxJmxEndpointExtension nonJmxJmxEndpointExtension() {
return new NonJmxJmxEndpointExtension();
}
}
@Endpoint(id = "test")
private static class TestEndpoint {
@@ -469,124 +569,4 @@ public class JmxAnnotationEndpointDiscovererTests {
}
@Configuration
static class EmptyConfiguration {
}
@Configuration
static class MultipleEndpointsConfiguration {
@Bean
public TestEndpoint testEndpoint() {
return new TestEndpoint();
}
@Bean
public TestJmxEndpoint testJmxEndpoint() {
return new TestJmxEndpoint();
}
@Bean
public NonJmxEndpoint nonJmxEndpoint() {
return new NonJmxEndpoint();
}
}
@Configuration
static class OverriddenOperationJmxEndpointConfiguration {
@Bean
public TestEndpoint testEndpoint() {
return new TestEndpoint();
}
@Bean
public TestJmxEndpointExtension testJmxEndpointExtension() {
return new TestJmxEndpointExtension();
}
}
@Configuration
static class AdditionalOperationJmxEndpointConfiguration {
@Bean
public TestEndpoint testEndpoint() {
return new TestEndpoint();
}
@Bean
public AdditionalOperationJmxEndpointExtension additionalOperationJmxEndpointExtension() {
return new AdditionalOperationJmxEndpointExtension();
}
}
@Configuration
static class AdditionalClashingOperationsConfiguration {
@Bean
public TestEndpoint testEndpoint() {
return new TestEndpoint();
}
@Bean
public ClashingOperationsJmxEndpointExtension clashingOperationsJmxEndpointExtension() {
return new ClashingOperationsJmxEndpointExtension();
}
}
@Configuration
static class ClashingJmxEndpointConfiguration {
@Bean
public TestEndpoint testEndpoint() {
return new TestEndpoint();
}
@Bean
public TestJmxEndpointExtension testExtensionOne() {
return new TestJmxEndpointExtension();
}
@Bean
public TestJmxEndpointExtension testExtensionTwo() {
return new TestJmxEndpointExtension();
}
}
@Configuration
static class ClashingStandardEndpointConfiguration {
@Bean
public TestEndpoint testEndpointTwo() {
return new TestEndpoint();
}
@Bean
public TestEndpoint testEndpointOne() {
return new TestEndpoint();
}
}
@Configuration
static class InvalidJmxExtensionConfiguration {
@Bean
public NonJmxEndpoint nonJmxEndpoint() {
return new NonJmxEndpoint();
}
@Bean
public NonJmxJmxEndpointExtension nonJmxJmxEndpointExtension() {
return new NonJmxJmxEndpointExtension();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,17 +16,19 @@
package org.springframework.boot.actuate.endpoint.web;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.assertj.core.api.Condition;
import org.junit.Test;
import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.OperationType;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link EndpointLinksResolver}.
@@ -57,13 +59,16 @@ public class EndpointLinksResolverTests {
@Test
public void resolvedLinksContainsALinkForEachEndpointOperation() {
List<WebOperation> operations = new ArrayList<>();
operations.add(operationWithPath("/alpha", "alpha"));
operations.add(operationWithPath("/alpha/{name}", "alpha-name"));
ExposableWebEndpoint endpoint = mock(ExposableWebEndpoint.class);
given(endpoint.getId()).willReturn("alpha");
given(endpoint.isEnableByDefault()).willReturn(true);
given(endpoint.getOperations()).willReturn(operations);
String requestUrl = "https://api.example.com/actuator";
Map<String, Link> links = this.linksResolver
.resolveLinks(
Arrays.asList(new EndpointInfo<>("alpha", true,
Arrays.asList(operationWithPath("/alpha", "alpha"),
operationWithPath("/alpha/{name}",
"alpha-name")))),
"https://api.example.com/actuator");
.resolveLinks(Collections.singletonList(endpoint), requestUrl);
assertThat(links).hasSize(3);
assertThat(links).hasEntrySatisfying("self",
linkWithHref("https://api.example.com/actuator"));
@@ -74,10 +79,14 @@ public class EndpointLinksResolverTests {
}
private WebOperation operationWithPath(String path, String id) {
return new WebOperation(OperationType.READ, null, false,
new OperationRequestPredicate(path, WebEndpointHttpMethod.GET,
Collections.emptyList(), Collections.emptyList()),
id);
WebOperationRequestPredicate predicate = new WebOperationRequestPredicate(path,
WebEndpointHttpMethod.GET, Collections.emptyList(),
Collections.emptyList());
WebOperation operation = mock(WebOperation.class);
given(operation.getId()).willReturn(id);
given(operation.getType()).willReturn(OperationType.READ);
given(operation.getRequestPredicate()).willReturn(predicate);
return operation;
}
private Condition<Link> linkWithHref(String href) {

View File

@@ -0,0 +1,69 @@
/*
* 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 java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link EndpointMediaTypes}.
*
* @author Phillip Webb
*/
public class EndpointMediaTypesTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void createWhenProducedIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Produced must not be null");
new EndpointMediaTypes(null, Collections.emptyList());
}
@Test
public void createWhenConsumedIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Consumed must not be null");
new EndpointMediaTypes(Collections.emptyList(), null);
}
@Test
public void getProducedShouldReturnProduced() {
List<String> produced = Arrays.asList("a", "b", "c");
EndpointMediaTypes types = new EndpointMediaTypes(produced,
Collections.emptyList());
assertThat(types.getProduced()).isEqualTo(produced);
}
@Test
public void getConsumedShouldReturnConsumed() {
List<String> consumed = Arrays.asList("a", "b", "c");
EndpointMediaTypes types = new EndpointMediaTypes(Collections.emptyList(),
consumed);
assertThat(types.getConsumed()).isEqualTo(consumed);
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link Link}.
*
* @author Phillip Webb
*/
public class LinkTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void createWhenHrefIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("HREF must not be null");
new Link(null);
}
@Test
public void getHrefShouldReturnHref() {
String href = "http://example.com";
Link link = new Link(href);
assertThat(link.getHref()).isEqualTo(href);
}
@Test
public void isTemplatedWhenContainsPlaceholderShouldReturnTrue() {
String href = "http://example.com/{path}";
Link link = new Link(href);
assertThat(link.isTemplated()).isTrue();
}
@Test
public void isTemplatedWhenContainsNoPlaceholderShouldReturnFalse() {
String href = "http://example.com/path";
Link link = new Link(href);
assertThat(link.isTemplated()).isFalse();
}
}

View File

@@ -0,0 +1,58 @@
/*
* 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.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link WebEndpointResponse}.
*
* @author Phillip Webb
*/
public class WebEndpointResponseTests {
@Test
public void createWithNoParamsShouldReturn200() {
WebEndpointResponse<Object> response = new WebEndpointResponse<>();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getBody()).isNull();
}
@Test
public void createWithStatusShouldReturnStatus() {
WebEndpointResponse<Object> response = new WebEndpointResponse<>(404);
assertThat(response.getStatus()).isEqualTo(404);
assertThat(response.getBody()).isNull();
}
@Test
public void createWithBodyShouldReturnBody() {
WebEndpointResponse<Object> response = new WebEndpointResponse<>("body");
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.getBody()).isEqualTo("body");
}
@Test
public void createWhithBodyAndStatusShouldReturnStatusAndBody() {
WebEndpointResponse<Object> response = new WebEndpointResponse<>("body", 500);
assertThat(response.getStatus()).isEqualTo(500);
assertThat(response.getBody()).isEqualTo("body");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,11 +23,11 @@ import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link OperationRequestPredicate}.
* Tests for {@link WebOperationRequestPredicate}.
*
* @author Andy Wilkinson
*/
public class OperationRequestPredicateTests {
public class WebOperationRequestPredicateTests {
@Test
public void predicatesWithIdenticalPathsAreEqual() {
@@ -63,8 +63,8 @@ public class OperationRequestPredicateTests {
.isEqualTo(predicateWithPath("/path/{foo2}/more/{bar2}"));
}
private OperationRequestPredicate predicateWithPath(String path) {
return new OperationRequestPredicate(path, WebEndpointHttpMethod.GET,
private WebOperationRequestPredicate predicateWithPath(String path) {
return new WebOperationRequestPredicate(path, WebEndpointHttpMethod.GET,
Collections.emptyList(), Collections.emptyList());
}

View File

@@ -14,11 +14,10 @@
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint.web;
package org.springframework.boot.actuate.endpoint.web.annotation;
import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -29,22 +28,17 @@ import java.util.function.Consumer;
import org.junit.Test;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper;
import org.springframework.boot.actuate.endpoint.reflect.ParameterMapper;
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
import org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
@@ -53,7 +47,6 @@ import org.springframework.lang.Nullable;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
@@ -362,47 +355,6 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
(context, client) -> clientConsumer.accept(client));
}
@Configuration
static class BaseConfiguration {
@Bean
public EndpointDelegate endpointDelegate() {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if (classLoader instanceof TomcatEmbeddedWebappClassLoader) {
Thread.currentThread().setContextClassLoader(classLoader.getParent());
}
try {
return mock(EndpointDelegate.class);
}
finally {
Thread.currentThread().setContextClassLoader(classLoader);
}
}
@Bean
public EndpointMediaTypes endpointMediaTypes() {
List<String> mediaTypes = Arrays.asList("application/vnd.test+json",
"application/json");
return new EndpointMediaTypes(mediaTypes, mediaTypes);
}
@Bean
public WebAnnotationEndpointDiscoverer webEndpointDiscoverer(
ApplicationContext applicationContext) {
ParameterMapper parameterMapper = new ConversionServiceParameterMapper(
DefaultConversionService.getSharedInstance());
return new WebAnnotationEndpointDiscoverer(applicationContext,
parameterMapper, endpointMediaTypes(),
EndpointPathResolver.useEndpointId(), null, null);
}
@Bean
public PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertyPlaceholderConfigurer();
}
}
@Configuration
@Import(BaseConfiguration.class)
protected static class TestEndpointConfiguration {

View File

@@ -0,0 +1,80 @@
/*
* 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.annotation;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.support.DefaultConversionService;
import static org.mockito.Mockito.mock;
/**
* Base configuration shared by tests.
*
* @author Andy Wilkinson
*/
@Configuration
class BaseConfiguration {
@Bean
public AbstractWebEndpointIntegrationTests.EndpointDelegate endpointDelegate() {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if (classLoader instanceof TomcatEmbeddedWebappClassLoader) {
Thread.currentThread().setContextClassLoader(classLoader.getParent());
}
try {
return mock(AbstractWebEndpointIntegrationTests.EndpointDelegate.class);
}
finally {
Thread.currentThread().setContextClassLoader(classLoader);
}
}
@Bean
public EndpointMediaTypes endpointMediaTypes() {
List<String> mediaTypes = Arrays.asList("application/vnd.test+json",
"application/json");
return new EndpointMediaTypes(mediaTypes, mediaTypes);
}
@Bean
public WebEndpointDiscoverer webEndpointDiscoverer(
ApplicationContext applicationContext) {
ParameterValueMapper parameterMapper = new ConversionServiceParameterValueMapper(
DefaultConversionService.getSharedInstance());
return new WebEndpointDiscoverer(applicationContext, parameterMapper,
endpointMediaTypes(), EndpointPathResolver.useEndpointId(),
Collections.emptyList(), Collections.emptyList());
}
@Bean
public PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertyPlaceholderConfigurer();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint.web;
package org.springframework.boot.actuate.endpoint.web.annotation;
import java.util.ArrayList;
import java.util.Arrays;
@@ -33,20 +33,21 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.OperationInvoker;
import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.boot.actuate.endpoint.cache.CachingOperationInvoker;
import org.springframework.boot.actuate.endpoint.cache.CachingOperationInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper;
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
import org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvoker;
import org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxEndpoint;
import org.springframework.boot.actuate.endpoint.web.AbstractWebEndpointIntegrationTests.BaseConfiguration;
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
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.WebEndpointHttpMethod;
import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -54,55 +55,55 @@ import org.springframework.context.annotation.Import;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.util.ReflectionUtils;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link WebAnnotationEndpointDiscoverer}.
* Tests for {@link WebEndpointDiscoverer}.
*
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Phillip Webb
*/
public class WebAnnotationEndpointDiscovererTests {
public class WebEndpointDiscovererTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Test
public void discoveryWorksWhenThereAreNoEndpoints() {
public void getEndpointsWhenNoEndpointBeansShouldReturnEmptyCollection() {
load(EmptyConfiguration.class,
(discoverer) -> assertThat(discoverer.discoverEndpoints()).isEmpty());
(discoverer) -> assertThat(discoverer.getEndpoints()).isEmpty());
}
@Test
public void webExtensionMustHaveEndpoint() {
public void getEndpointsWhenWebExtensionIsMissingEndpointShouldThrowException() {
load(TestWebEndpointExtensionConfiguration.class, (discoverer) -> {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Invalid extension");
this.thrown.expectMessage(TestWebEndpointExtension.class.getName());
this.thrown.expectMessage("no endpoint found");
this.thrown.expectMessage(TestEndpoint.class.getName());
discoverer.discoverEndpoints();
this.thrown.expectMessage(
"Invalid extension 'endpointExtension': no endpoint found with type '"
+ TestEndpoint.class.getName() + "'");
discoverer.getEndpoints();
});
}
@Test
public void onlyWebEndpointsAreDiscovered() {
public void getEndpointsWhenHasFilteredEndpointShouldOnlyDiscoverWebEndpoints() {
load(MultipleEndpointsConfiguration.class, (discoverer) -> {
Map<String, EndpointInfo<WebOperation>> endpoints = mapEndpoints(
discoverer.discoverEndpoints());
Map<String, ExposableWebEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test");
});
}
@Test
public void oneOperationIsDiscoveredWhenExtensionOverridesOperation() {
public void getEndpointsWhenHasWebExtensionShouldOverrideStandardEndpoint() {
load(OverriddenOperationWebEndpointExtensionConfiguration.class, (discoverer) -> {
Map<String, EndpointInfo<WebOperation>> endpoints = mapEndpoints(
discoverer.discoverEndpoints());
Map<String, ExposableWebEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test");
EndpointInfo<WebOperation> endpoint = endpoints.get("test");
ExposableWebEndpoint endpoint = endpoints.get("test");
assertThat(requestPredicates(endpoint)).has(
requestPredicates(path("test").httpMethod(WebEndpointHttpMethod.GET)
.consumes().produces("application/json")));
@@ -110,12 +111,12 @@ public class WebAnnotationEndpointDiscovererTests {
}
@Test
public void twoOperationsAreDiscoveredWhenExtensionAddsOperation() {
public void getEndpointsWhenExtensionAddsOperationShouldHaveBothOperations() {
load(AdditionalOperationWebEndpointConfiguration.class, (discoverer) -> {
Map<String, EndpointInfo<WebOperation>> endpoints = mapEndpoints(
discoverer.discoverEndpoints());
Map<String, ExposableWebEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test");
EndpointInfo<WebOperation> endpoint = endpoints.get("test");
ExposableWebEndpoint endpoint = endpoints.get("test");
assertThat(requestPredicates(endpoint)).has(requestPredicates(
path("test").httpMethod(WebEndpointHttpMethod.GET).consumes()
.produces("application/json"),
@@ -125,12 +126,12 @@ public class WebAnnotationEndpointDiscovererTests {
}
@Test
public void predicateForWriteOperationThatReturnsVoidHasNoProducedMediaTypes() {
public void getEndpointsWhenPredicateForWriteOperationThatReturnsVoidShouldHaveNoProducedMediaTypes() {
load(VoidWriteOperationEndpointConfiguration.class, (discoverer) -> {
Map<String, EndpointInfo<WebOperation>> endpoints = mapEndpoints(
discoverer.discoverEndpoints());
Map<String, ExposableWebEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("voidwrite");
EndpointInfo<WebOperation> endpoint = endpoints.get("voidwrite");
ExposableWebEndpoint endpoint = endpoints.get("voidwrite");
assertThat(requestPredicates(endpoint)).has(requestPredicates(
path("voidwrite").httpMethod(WebEndpointHttpMethod.POST).produces()
.consumes("application/json")));
@@ -138,91 +139,78 @@ public class WebAnnotationEndpointDiscovererTests {
}
@Test
public void discoveryFailsWhenTwoExtensionsHaveTheSameEndpointType() {
public void getEndpointsWhenTwoExtensionsHaveTheSameEndpointTypeShouldThrowException() {
load(ClashingWebEndpointConfiguration.class, (discoverer) -> {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Found two extensions for the same endpoint");
this.thrown.expectMessage(TestEndpoint.class.getName());
this.thrown.expectMessage(TestWebEndpointExtension.class.getName());
discoverer.discoverEndpoints();
this.thrown.expectMessage("Found multiple extensions for the endpoint bean "
+ "testEndpoint (testExtensionOne, testExtensionTwo)");
discoverer.getEndpoints();
});
}
@Test
public void discoveryFailsWhenTwoStandardEndpointsHaveTheSameId() {
public void getEndpointsWhenTwoStandardEndpointsHaveTheSameIdShouldThrowException() {
load(ClashingStandardEndpointConfiguration.class, (discoverer) -> {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Found two endpoints with the id 'test': ");
discoverer.discoverEndpoints();
discoverer.getEndpoints();
});
}
@Test
public void discoveryFailsWhenEndpointHasClashingOperations() {
public void getEndpointsWhenWhenEndpointHasTwoOperationsWithTheSameNameShouldThrowException() {
load(ClashingOperationsEndpointConfiguration.class, (discoverer) -> {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage(
"Found multiple web operations with matching request predicates:");
this.thrown.expectMessage(ReflectionUtils
.findMethod(ClashingOperationsEndpoint.class, "getAll").toString());
this.thrown.expectMessage(ReflectionUtils
.findMethod(ClashingOperationsEndpoint.class, "getAgain").toString());
discoverer.discoverEndpoints();
this.thrown.expectMessage("Unable to map duplicate endpoint operations: "
+ "[web request predicate GET to path 'test' "
+ "produces: application/json] to clashingOperationsEndpoint");
discoverer.getEndpoints();
});
}
@Test
public void discoveryFailsWhenExtensionIsNotCompatibleWithTheEndpointType() {
public void getEndpointsWhenExtensionIsNotCompatibleWithTheEndpointTypeShouldThrowException() {
load(InvalidWebExtensionConfiguration.class, (discoverer) -> {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Invalid extension");
this.thrown.expectMessage(NonWebWebEndpointExtension.class.getName());
this.thrown.expectMessage(NonWebEndpoint.class.getName());
discoverer.discoverEndpoints();
this.thrown.expectMessage("Endpoint bean 'nonWebEndpoint' cannot support the "
+ "extension bean 'nonWebWebEndpointExtension'");
discoverer.getEndpoints();
});
}
@Test
public void twoOperationsOnSameEndpointClashWhenSelectorsHaveDifferentNames() {
public void getEndpointsWhenWhenExtensionHasTwoOperationsWithTheSameNameShouldThrowException() {
load(ClashingSelectorsWebEndpointExtensionConfiguration.class, (discoverer) -> {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage(
"Found multiple web operations with matching request predicates:");
this.thrown.expectMessage(ReflectionUtils
.findMethod(ClashingSelectorsWebEndpointExtension.class, "readOne",
String.class, String.class)
.toString());
this.thrown.expectMessage(ReflectionUtils
.findMethod(ClashingSelectorsWebEndpointExtension.class, "readTwo",
String.class, String.class)
.toString());
discoverer.discoverEndpoints();
this.thrown.expectMessage("Unable to map duplicate endpoint operations");
this.thrown.expectMessage("to testEndpoint (clashingSelectorsExtension)");
discoverer.getEndpoints();
});
}
@Test
public void endpointMainReadOperationIsCachedWithMatchingId() {
public void getEndpointsWhenHasCacheWithTtlShouldCacheReadOperationWithTtlValue() {
load((id) -> 500L, (id) -> id, TestEndpointConfiguration.class, (discoverer) -> {
Map<String, EndpointInfo<WebOperation>> endpoints = mapEndpoints(
discoverer.discoverEndpoints());
Map<String, ExposableWebEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test");
EndpointInfo<WebOperation> endpoint = endpoints.get("test");
ExposableWebEndpoint endpoint = endpoints.get("test");
assertThat(endpoint.getOperations()).hasSize(1);
OperationInvoker operationInvoker = endpoint.getOperations().iterator().next()
.getInvoker();
assertThat(operationInvoker).isInstanceOf(CachingOperationInvoker.class);
assertThat(((CachingOperationInvoker) operationInvoker).getTimeToLive())
WebOperation operation = endpoint.getOperations().iterator().next();
Object invoker = ReflectionTestUtils.getField(operation, "invoker");
assertThat(invoker).isInstanceOf(CachingOperationInvoker.class);
assertThat(((CachingOperationInvoker) invoker).getTimeToLive())
.isEqualTo(500);
});
}
@Test
public void operationsThatReturnResourceProduceApplicationOctetStream() {
public void getEndpointsWhenOperationReturnsResourceShouldProduceApplicationOctetStream() {
load(ResourceEndpointConfiguration.class, (discoverer) -> {
Map<String, EndpointInfo<WebOperation>> endpoints = mapEndpoints(
discoverer.discoverEndpoints());
Map<String, ExposableWebEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("resource");
EndpointInfo<WebOperation> endpoint = endpoints.get("resource");
ExposableWebEndpoint endpoint = endpoints.get("resource");
assertThat(requestPredicates(endpoint)).has(requestPredicates(
path("resource").httpMethod(WebEndpointHttpMethod.GET).consumes()
.produces("application/octet-stream")));
@@ -230,12 +218,12 @@ public class WebAnnotationEndpointDiscovererTests {
}
@Test
public void operationCanProduceCustomMediaTypes() {
public void getEndpointsWhenHasCustomMediaTypeShouldProduceCustomMediaType() {
load(CustomMediaTypesEndpointConfiguration.class, (discoverer) -> {
Map<String, EndpointInfo<WebOperation>> endpoints = mapEndpoints(
discoverer.discoverEndpoints());
Map<String, ExposableWebEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("custommediatypes");
EndpointInfo<WebOperation> endpoint = endpoints.get("custommediatypes");
ExposableWebEndpoint endpoint = endpoints.get("custommediatypes");
assertThat(requestPredicates(endpoint)).has(requestPredicates(
path("custommediatypes").httpMethod(WebEndpointHttpMethod.GET)
.consumes().produces("text/plain"),
@@ -247,14 +235,14 @@ public class WebAnnotationEndpointDiscovererTests {
}
@Test
public void endpointPathCanBeCustomized() {
public void getEndpointsWhenHasCustomPathShouldReturnCustomPath() {
load((id) -> null, (id) -> "custom/" + id,
AdditionalOperationWebEndpointConfiguration.class, (discoverer) -> {
Map<String, EndpointInfo<WebOperation>> endpoints = mapEndpoints(
discoverer.discoverEndpoints());
Map<String, ExposableWebEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test");
EndpointInfo<WebOperation> endpoint = endpoints.get("test");
Condition<List<? extends OperationRequestPredicate>> expected = requestPredicates(
ExposableWebEndpoint endpoint = endpoints.get("test");
Condition<List<? extends WebOperationRequestPredicate>> expected = requestPredicates(
path("custom/test").httpMethod(WebEndpointHttpMethod.GET)
.consumes().produces("application/json"),
path("custom/test/{id}").httpMethod(WebEndpointHttpMethod.GET)
@@ -263,26 +251,25 @@ public class WebAnnotationEndpointDiscovererTests {
});
}
private void load(Class<?> configuration,
Consumer<WebAnnotationEndpointDiscoverer> consumer) {
private void load(Class<?> configuration, Consumer<WebEndpointDiscoverer> consumer) {
this.load((id) -> null, (id) -> id, configuration, consumer);
}
private void load(Function<String, Long> timeToLive,
EndpointPathResolver endpointPathResolver, Class<?> configuration,
Consumer<WebAnnotationEndpointDiscoverer> consumer) {
Consumer<WebEndpointDiscoverer> consumer) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
configuration);
try {
ConversionServiceParameterMapper parameterMapper = new ConversionServiceParameterMapper(
ConversionServiceParameterValueMapper parameterMapper = new ConversionServiceParameterValueMapper(
DefaultConversionService.getSharedInstance());
EndpointMediaTypes mediaTypes = new EndpointMediaTypes(
Collections.singletonList("application/json"),
Collections.singletonList("application/json"));
WebAnnotationEndpointDiscoverer discoverer = new WebAnnotationEndpointDiscoverer(
context, parameterMapper, mediaTypes, endpointPathResolver,
WebEndpointDiscoverer discoverer = new WebEndpointDiscoverer(context,
parameterMapper, mediaTypes, endpointPathResolver,
Collections.singleton(new CachingOperationInvokerAdvisor(timeToLive)),
null);
Collections.emptyList());
consumer.accept(discoverer);
}
finally {
@@ -290,27 +277,27 @@ public class WebAnnotationEndpointDiscovererTests {
}
}
private Map<String, EndpointInfo<WebOperation>> mapEndpoints(
Collection<EndpointInfo<WebOperation>> endpoints) {
Map<String, EndpointInfo<WebOperation>> endpointById = new HashMap<>();
private Map<String, ExposableWebEndpoint> mapEndpoints(
Collection<ExposableWebEndpoint> endpoints) {
Map<String, ExposableWebEndpoint> endpointById = new HashMap<>();
endpoints.forEach((endpoint) -> endpointById.put(endpoint.getId(), endpoint));
return endpointById;
}
private List<OperationRequestPredicate> requestPredicates(
EndpointInfo<WebOperation> endpoint) {
private List<WebOperationRequestPredicate> requestPredicates(
ExposableWebEndpoint endpoint) {
return endpoint.getOperations().stream().map(WebOperation::getRequestPredicate)
.collect(Collectors.toList());
}
private Condition<List<? extends OperationRequestPredicate>> requestPredicates(
private Condition<List<? extends WebOperationRequestPredicate>> requestPredicates(
RequestPredicateMatcher... matchers) {
return new Condition<>((predicates) -> {
if (predicates.size() != matchers.length) {
return false;
}
Map<OperationRequestPredicate, Long> matchCounts = new HashMap<>();
for (OperationRequestPredicate predicate : predicates) {
Map<WebOperationRequestPredicate, Long> matchCounts = new HashMap<>();
for (WebOperationRequestPredicate predicate : predicates) {
matchCounts.put(predicate, Stream.of(matchers)
.filter(matcher -> matcher.matches(predicate)).count());
}
@@ -327,165 +314,6 @@ public class WebAnnotationEndpointDiscovererTests {
}
@EndpointWebExtension(endpoint = TestEndpoint.class)
static class TestWebEndpointExtension {
@ReadOperation
public Object getAll() {
return null;
}
@ReadOperation
public Object getOne(@Selector String id) {
return null;
}
@WriteOperation
public void update(String foo, String bar) {
}
public void someOtherMethod() {
}
}
@Endpoint(id = "test")
static class TestEndpoint {
@ReadOperation
public Object getAll() {
return null;
}
}
@EndpointWebExtension(endpoint = TestEndpoint.class)
static class OverriddenOperationWebEndpointExtension {
@ReadOperation
public Object getAll() {
return null;
}
}
@EndpointWebExtension(endpoint = TestEndpoint.class)
static class AdditionalOperationWebEndpointExtension {
@ReadOperation
public Object getOne(@Selector String id) {
return null;
}
}
@Endpoint(id = "test")
static class ClashingOperationsEndpoint {
@ReadOperation
public Object getAll() {
return null;
}
@ReadOperation
public Object getAgain() {
return null;
}
}
@EndpointWebExtension(endpoint = TestEndpoint.class)
static class ClashingOperationsWebEndpointExtension {
@ReadOperation
public Object getAll() {
return null;
}
@ReadOperation
public Object getAgain() {
return null;
}
}
@EndpointWebExtension(endpoint = TestEndpoint.class)
static class ClashingSelectorsWebEndpointExtension {
@ReadOperation
public Object readOne(@Selector String oneA, @Selector String oneB) {
return null;
}
@ReadOperation
public Object readTwo(@Selector String twoA, @Selector String twoB) {
return null;
}
}
@JmxEndpoint(id = "nonweb")
static class NonWebEndpoint {
@ReadOperation
public Object getData() {
return null;
}
}
@EndpointWebExtension(endpoint = NonWebEndpoint.class)
static class NonWebWebEndpointExtension {
@ReadOperation
public Object getSomething(@Selector String name) {
return null;
}
}
@Endpoint(id = "voidwrite")
static class VoidWriteOperationEndpoint {
@WriteOperation
public void write(String foo, String bar) {
}
}
@Endpoint(id = "resource")
static class ResourceEndpoint {
@ReadOperation
public Resource read() {
return new ByteArrayResource(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
}
}
@Endpoint(id = "custommediatypes")
static class CustomMediaTypesEndpoint {
@ReadOperation(produces = "text/plain")
public String read() {
return "read";
}
@WriteOperation(produces = { "a/b", "c/d" })
public String write() {
return "write";
}
@DeleteOperation(produces = "text/plain")
public String delete() {
return "delete";
}
}
@Configuration
static class MultipleEndpointsConfiguration {
@@ -660,6 +488,165 @@ public class WebAnnotationEndpointDiscovererTests {
}
@EndpointWebExtension(endpoint = TestEndpoint.class)
static class TestWebEndpointExtension {
@ReadOperation
public Object getAll() {
return null;
}
@ReadOperation
public Object getOne(@Selector String id) {
return null;
}
@WriteOperation
public void update(String foo, String bar) {
}
public void someOtherMethod() {
}
}
@Endpoint(id = "test")
static class TestEndpoint {
@ReadOperation
public Object getAll() {
return null;
}
}
@EndpointWebExtension(endpoint = TestEndpoint.class)
static class OverriddenOperationWebEndpointExtension {
@ReadOperation
public Object getAll() {
return null;
}
}
@EndpointWebExtension(endpoint = TestEndpoint.class)
static class AdditionalOperationWebEndpointExtension {
@ReadOperation
public Object getOne(@Selector String id) {
return null;
}
}
@Endpoint(id = "test")
static class ClashingOperationsEndpoint {
@ReadOperation
public Object getAll() {
return null;
}
@ReadOperation
public Object getAgain() {
return null;
}
}
@EndpointWebExtension(endpoint = TestEndpoint.class)
static class ClashingOperationsWebEndpointExtension {
@ReadOperation
public Object getAll() {
return null;
}
@ReadOperation
public Object getAgain() {
return null;
}
}
@EndpointWebExtension(endpoint = TestEndpoint.class)
static class ClashingSelectorsWebEndpointExtension {
@ReadOperation
public Object readOne(@Selector String oneA, @Selector String oneB) {
return null;
}
@ReadOperation
public Object readTwo(@Selector String twoA, @Selector String twoB) {
return null;
}
}
@JmxEndpoint(id = "nonweb")
static class NonWebEndpoint {
@ReadOperation
public Object getData() {
return null;
}
}
@EndpointWebExtension(endpoint = NonWebEndpoint.class)
static class NonWebWebEndpointExtension {
@ReadOperation
public Object getSomething(@Selector String name) {
return null;
}
}
@Endpoint(id = "voidwrite")
static class VoidWriteOperationEndpoint {
@WriteOperation
public void write(String foo, String bar) {
}
}
@Endpoint(id = "resource")
static class ResourceEndpoint {
@ReadOperation
public Resource read() {
return new ByteArrayResource(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
}
}
@Endpoint(id = "custommediatypes")
static class CustomMediaTypesEndpoint {
@ReadOperation(produces = "text/plain")
public String read() {
return "read";
}
@WriteOperation(produces = { "a/b", "c/d" })
public String write() {
return "write";
}
@DeleteOperation(produces = "text/plain")
public String delete() {
return "delete";
}
}
private static final class RequestPredicateMatcher {
private final String path;
@@ -689,7 +676,7 @@ public class WebAnnotationEndpointDiscovererTests {
return this;
}
private boolean matches(OperationRequestPredicate predicate) {
private boolean matches(WebOperationRequestPredicate predicate) {
return (this.path == null || this.path.equals(predicate.getPath()))
&& (this.httpMethod == null
|| this.httpMethod == predicate.getHttpMethod())

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,10 +27,9 @@ import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.model.Resource;
import org.glassfish.jersey.servlet.ServletContainer;
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.web.AbstractWebEndpointIntegrationTests;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.boot.actuate.endpoint.web.annotation.AbstractWebEndpointIntegrationTests;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
import org.springframework.boot.endpoint.web.EndpointMapping;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
@@ -82,13 +81,13 @@ public class JerseyWebEndpointIntegrationTests extends
@Bean
public ResourceConfig resourceConfig(Environment environment,
EndpointDiscoverer<WebOperation> endpointDiscoverer,
WebEndpointDiscoverer endpointDiscoverer,
EndpointMediaTypes endpointMediaTypes) {
ResourceConfig resourceConfig = new ResourceConfig();
Collection<Resource> resources = new JerseyEndpointResourceFactory()
.createEndpointResources(
new EndpointMapping(environment.getProperty("endpointPath")),
endpointDiscoverer.discoverEndpoints(), endpointMediaTypes);
endpointDiscoverer.getEndpoints(), endpointMediaTypes);
resourceConfig.registerResources(new HashSet<>(resources));
resourceConfig.register(JacksonFeature.class);
resourceConfig.register(new ObjectMapperContextResolver(new ObjectMapper()),

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,10 +20,9 @@ import java.util.Arrays;
import org.junit.Test;
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.web.AbstractWebEndpointIntegrationTests;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.boot.actuate.endpoint.web.annotation.AbstractWebEndpointIntegrationTests;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
import org.springframework.boot.endpoint.web.EndpointMapping;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
@@ -112,15 +111,14 @@ public class WebFluxEndpointIntegrationTests
@Bean
public WebFluxEndpointHandlerMapping webEndpointHandlerMapping(
Environment environment,
EndpointDiscoverer<WebOperation> endpointDiscoverer,
Environment environment, WebEndpointDiscoverer endpointDiscoverer,
EndpointMediaTypes endpointMediaTypes) {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(Arrays.asList("http://example.com"));
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST"));
return new WebFluxEndpointHandlerMapping(
new EndpointMapping(environment.getProperty("endpointPath")),
endpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
endpointDiscoverer.getEndpoints(), endpointMediaTypes,
corsConfiguration);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,10 +20,9 @@ import java.util.Arrays;
import org.junit.Test;
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.web.AbstractWebEndpointIntegrationTests;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.boot.actuate.endpoint.web.annotation.AbstractWebEndpointIntegrationTests;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
import org.springframework.boot.endpoint.web.EndpointMapping;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
@@ -105,15 +104,14 @@ public class MvcWebEndpointIntegrationTests extends
@Bean
public WebMvcEndpointHandlerMapping webEndpointHandlerMapping(
Environment environment,
EndpointDiscoverer<WebOperation> webEndpointDiscoverer,
Environment environment, WebEndpointDiscoverer endpointDiscoverer,
EndpointMediaTypes endpointMediaTypes) {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(Arrays.asList("http://example.com"));
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST"));
return new WebMvcEndpointHandlerMapping(
new EndpointMapping(environment.getProperty("endpointPath")),
webEndpointDiscoverer.discoverEndpoints(), endpointMediaTypes,
endpointDiscoverer.getEndpoints(), endpointMediaTypes,
corsConfiguration);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.boot.actuate.endpoint.web.test;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -28,11 +29,11 @@ import org.glassfish.jersey.server.model.Resource;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.InitializationError;
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper;
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.web.jersey.JerseyEndpointResourceFactory;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
@@ -97,12 +98,13 @@ class JerseyEndpointsRunner extends AbstractWebEndpointRunner {
ActuatorMediaType.V2_JSON);
EndpointMediaTypes endpointMediaTypes = new EndpointMediaTypes(mediaTypes,
mediaTypes);
WebAnnotationEndpointDiscoverer discoverer = new WebAnnotationEndpointDiscoverer(
this.applicationContext, new ConversionServiceParameterMapper(),
endpointMediaTypes, EndpointPathResolver.useEndpointId(), null, null);
WebEndpointDiscoverer discoverer = new WebEndpointDiscoverer(
this.applicationContext, new ConversionServiceParameterValueMapper(),
endpointMediaTypes, EndpointPathResolver.useEndpointId(),
Collections.emptyList(), Collections.emptyList());
Collection<Resource> resources = new JerseyEndpointResourceFactory()
.createEndpointResources(new EndpointMapping("/actuator"),
discoverer.discoverEndpoints(), endpointMediaTypes);
discoverer.getEndpoints(), endpointMediaTypes);
config.registerResources(new HashSet<>(resources));
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,16 +17,17 @@
package org.springframework.boot.actuate.endpoint.web.test;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.InitializationError;
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper;
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.web.reactive.WebFluxEndpointHandlerMapping;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
@@ -103,11 +104,12 @@ class WebFluxEndpointsRunner extends AbstractWebEndpointRunner {
ActuatorMediaType.V2_JSON);
EndpointMediaTypes endpointMediaTypes = new EndpointMediaTypes(mediaTypes,
mediaTypes);
WebAnnotationEndpointDiscoverer discoverer = new WebAnnotationEndpointDiscoverer(
this.applicationContext, new ConversionServiceParameterMapper(),
endpointMediaTypes, EndpointPathResolver.useEndpointId(), null, null);
WebEndpointDiscoverer discoverer = new WebEndpointDiscoverer(
this.applicationContext, new ConversionServiceParameterValueMapper(),
endpointMediaTypes, EndpointPathResolver.useEndpointId(),
Collections.emptyList(), Collections.emptyList());
return new WebFluxEndpointHandlerMapping(new EndpointMapping("/actuator"),
discoverer.discoverEndpoints(), endpointMediaTypes,
discoverer.getEndpoints(), endpointMediaTypes,
new CorsConfiguration());
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,16 +17,17 @@
package org.springframework.boot.actuate.endpoint.web.test;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.InitializationError;
import org.springframework.boot.actuate.endpoint.convert.ConversionServiceParameterMapper;
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
@@ -86,11 +87,12 @@ class WebMvcEndpointRunner extends AbstractWebEndpointRunner {
ActuatorMediaType.V2_JSON);
EndpointMediaTypes endpointMediaTypes = new EndpointMediaTypes(mediaTypes,
mediaTypes);
WebAnnotationEndpointDiscoverer discoverer = new WebAnnotationEndpointDiscoverer(
this.applicationContext, new ConversionServiceParameterMapper(),
endpointMediaTypes, EndpointPathResolver.useEndpointId(), null, null);
WebEndpointDiscoverer discoverer = new WebEndpointDiscoverer(
this.applicationContext, new ConversionServiceParameterValueMapper(),
endpointMediaTypes, EndpointPathResolver.useEndpointId(),
Collections.emptyList(), Collections.emptyList());
return new WebMvcEndpointHandlerMapping(new EndpointMapping("/actuator"),
discoverer.discoverEndpoints(), endpointMediaTypes,
discoverer.getEndpoints(), endpointMediaTypes,
new CorsConfiguration());
}