From 84d0e8acd8ab8ecfcfa55a6d0bf17331ecb988d5 Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Tue, 11 Oct 2016 12:18:39 -0700 Subject: [PATCH 1/6] Fix EndpointHandlerMappingTests path references Fix the TestEndpoint constructor to use an ID parameter rather than path. --- .../mvc/EndpointHandlerMappingTests.java | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingTests.java index 8e162f97f4d..77c8dc1336e 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingTests.java @@ -41,6 +41,7 @@ import static org.assertj.core.api.Assertions.assertThat; public class EndpointHandlerMappingTests { private final StaticApplicationContext context = new StaticApplicationContext(); + private Method method; @Before @@ -50,8 +51,8 @@ public class EndpointHandlerMappingTests { @Test public void withoutPrefix() throws Exception { - TestMvcEndpoint endpointA = new TestMvcEndpoint(new TestEndpoint("/a")); - TestMvcEndpoint endpointB = new TestMvcEndpoint(new TestEndpoint("/b")); + TestMvcEndpoint endpointA = new TestMvcEndpoint(new TestEndpoint("a")); + TestMvcEndpoint endpointB = new TestMvcEndpoint(new TestEndpoint("b")); EndpointHandlerMapping mapping = new EndpointHandlerMapping( Arrays.asList(endpointA, endpointB)); mapping.setApplicationContext(this.context); @@ -65,8 +66,8 @@ public class EndpointHandlerMappingTests { @Test public void withPrefix() throws Exception { - TestMvcEndpoint endpointA = new TestMvcEndpoint(new TestEndpoint("/a")); - TestMvcEndpoint endpointB = new TestMvcEndpoint(new TestEndpoint("/b")); + TestMvcEndpoint endpointA = new TestMvcEndpoint(new TestEndpoint("a")); + TestMvcEndpoint endpointB = new TestMvcEndpoint(new TestEndpoint("b")); EndpointHandlerMapping mapping = new EndpointHandlerMapping( Arrays.asList(endpointA, endpointB)); mapping.setApplicationContext(this.context); @@ -81,7 +82,7 @@ public class EndpointHandlerMappingTests { @Test(expected = HttpRequestMethodNotSupportedException.class) public void onlyGetHttpMethodForNonActionEndpoints() throws Exception { - TestActionEndpoint endpoint = new TestActionEndpoint(new TestEndpoint("/a")); + TestActionEndpoint endpoint = new TestActionEndpoint(new TestEndpoint("a")); EndpointHandlerMapping mapping = new EndpointHandlerMapping( Arrays.asList(endpoint)); mapping.setApplicationContext(this.context); @@ -92,7 +93,7 @@ public class EndpointHandlerMappingTests { @Test public void postHttpMethodForActionEndpoints() throws Exception { - TestActionEndpoint endpoint = new TestActionEndpoint(new TestEndpoint("/a")); + TestActionEndpoint endpoint = new TestActionEndpoint(new TestEndpoint("a")); EndpointHandlerMapping mapping = new EndpointHandlerMapping( Arrays.asList(endpoint)); mapping.setApplicationContext(this.context); @@ -102,7 +103,7 @@ public class EndpointHandlerMappingTests { @Test(expected = HttpRequestMethodNotSupportedException.class) public void onlyPostHttpMethodForActionEndpoints() throws Exception { - TestActionEndpoint endpoint = new TestActionEndpoint(new TestEndpoint("/a")); + TestActionEndpoint endpoint = new TestActionEndpoint(new TestEndpoint("a")); EndpointHandlerMapping mapping = new EndpointHandlerMapping( Arrays.asList(endpoint)); mapping.setApplicationContext(this.context); @@ -113,7 +114,7 @@ public class EndpointHandlerMappingTests { @Test public void disabled() throws Exception { - TestMvcEndpoint endpoint = new TestMvcEndpoint(new TestEndpoint("/a")); + TestMvcEndpoint endpoint = new TestMvcEndpoint(new TestEndpoint("a")); EndpointHandlerMapping mapping = new EndpointHandlerMapping( Arrays.asList(endpoint)); mapping.setDisabled(true); @@ -124,8 +125,8 @@ public class EndpointHandlerMappingTests { @Test public void duplicatePath() throws Exception { - TestMvcEndpoint endpoint = new TestMvcEndpoint(new TestEndpoint("/a")); - TestActionEndpoint other = new TestActionEndpoint(new TestEndpoint("/a")); + TestMvcEndpoint endpoint = new TestMvcEndpoint(new TestEndpoint("a")); + TestActionEndpoint other = new TestActionEndpoint(new TestEndpoint("a")); EndpointHandlerMapping mapping = new EndpointHandlerMapping( Arrays.asList(endpoint, other)); mapping.setDisabled(true); @@ -141,8 +142,8 @@ public class EndpointHandlerMappingTests { private static class TestEndpoint extends AbstractEndpoint { - TestEndpoint(String path) { - super(path); + TestEndpoint(String id) { + super(id); } @Override From 0f5007d69d7bf762f4dff02ed80c2462f9f59495 Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Tue, 11 Oct 2016 15:27:49 -0700 Subject: [PATCH 2/6] Drop superfluous disabled check Remove EndpointWebMvcManagementContextConfiguration `disabled` logic since the configuration should even be processed when the management port is `-1`. Closes gh-7154 --- .../EndpointWebMvcManagementContextConfiguration.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcManagementContextConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcManagementContextConfiguration.java index 154d7e9fb1d..d4752b5441b 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcManagementContextConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcManagementContextConfiguration.java @@ -83,12 +83,7 @@ public class EndpointWebMvcManagementContextConfiguration { CorsConfiguration corsConfiguration = getCorsConfiguration(this.corsProperties); EndpointHandlerMapping mapping = new EndpointHandlerMapping(endpoints, corsConfiguration); - boolean disabled = this.managementServerProperties.getPort() != null - && this.managementServerProperties.getPort() == -1; - mapping.setDisabled(disabled); - if (!disabled) { - mapping.setPrefix(this.managementServerProperties.getContextPath()); - } + mapping.setPrefix(this.managementServerProperties.getContextPath()); if (this.mappingCustomizers != null) { for (EndpointHandlerMappingCustomizer customizer : this.mappingCustomizers) { customizer.customize(mapping); From 90722a9bca89a933d375077945cf5497f7643bcb Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Tue, 11 Oct 2016 14:59:03 -0700 Subject: [PATCH 3/6] Add @ConditionalOnCloudPlatform Add a @ConditionalOnCloudPlatform annotation that matches based on the active ClouldPlatform. Fixes gh-7155 --- .../condition/ConditionalOnCloudPlatform.java | 46 ++++++++++ .../condition/OnCloudPlatformCondition.java | 55 ++++++++++++ .../ConditionalOnCloudPlatformTests.java | 84 +++++++++++++++++++ 3 files changed, 185 insertions(+) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnCloudPlatform.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnCloudPlatformCondition.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnCloudPlatformTests.java diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnCloudPlatform.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnCloudPlatform.java new file mode 100644 index 00000000000..e0c367752b3 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnCloudPlatform.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2016 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.autoconfigure.condition; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.boot.cloud.CloudPlatform; +import org.springframework.context.annotation.Conditional; + +/** + * {@link Conditional} that matches when the specified cloud platform is active. + * + * @author Madhura Bhave + * @since 1.5.0 + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Conditional(OnCloudPlatformCondition.class) +public @interface ConditionalOnCloudPlatform { + + /** + * The {@link CloudPlatform cloud platform} that must be active. + * @return the expected cloud platform + */ + CloudPlatform value(); + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnCloudPlatformCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnCloudPlatformCondition.java new file mode 100644 index 00000000000..4a704254a4a --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnCloudPlatformCondition.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2016 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.autoconfigure.condition; + +import java.util.Map; + +import org.springframework.boot.cloud.CloudPlatform; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.env.Environment; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * {@link Condition} that checks for a required {@link CloudPlatform}. + * + * @author Madhura Bhave + * @see ConditionalOnCloudPlatform + */ +class OnCloudPlatformCondition extends SpringBootCondition { + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, + AnnotatedTypeMetadata metadata) { + Map attributes = metadata + .getAnnotationAttributes(ConditionalOnCloudPlatform.class.getName()); + CloudPlatform cloudPlatform = (CloudPlatform) attributes.get("value"); + return getMatchOutcome(context.getEnvironment(), cloudPlatform); + } + + private ConditionOutcome getMatchOutcome(Environment environment, + CloudPlatform cloudPlatform) { + String name = cloudPlatform.name(); + ConditionMessage.Builder message = ConditionMessage + .forCondition(ConditionalOnCloudPlatform.class); + if (cloudPlatform.isActive(environment)) { + return ConditionOutcome.match(message.foundExactly(name)); + } + return ConditionOutcome.noMatch(message.didNotFind(name).atAll()); + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnCloudPlatformTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnCloudPlatformTests.java new file mode 100644 index 00000000000..f2de5088d28 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnCloudPlatformTests.java @@ -0,0 +1,84 @@ +/* + * Copyright 2012-2016 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.autoconfigure.condition; + +import org.junit.Test; + +import org.springframework.boot.cloud.CloudPlatform; +import org.springframework.boot.test.util.EnvironmentTestUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConditionalOnCloudPlatform}. + */ +public class ConditionalOnCloudPlatformTests { + + private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + + @Test + public void outcomeWhenCloudfoundryPlatformNotPresentShouldNotMatch() + throws Exception { + load(CloudFoundryPlatformConfig.class, ""); + assertThat(this.context.containsBean("foo")).isFalse(); + } + + @Test + public void outcomeWhenCloudfoundryPlatformPresentShouldMatch() throws Exception { + load(CloudFoundryPlatformConfig.class, "VCAP_APPLICATION:---"); + assertThat(this.context.containsBean("foo")).isTrue(); + } + + @Test + public void outcomeWhenCloudfoundryPlatformPresentAndMethodTargetShouldMatch() + throws Exception { + load(CloudFoundryPlatformOnMethodConfig.class, "VCAP_APPLICATION:---"); + assertThat(this.context.containsBean("foo")).isTrue(); + } + + private void load(Class config, String... environment) { + EnvironmentTestUtils.addEnvironment(this.context, environment); + this.context.register(config); + this.context.refresh(); + } + + @Configuration + @ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY) + static class CloudFoundryPlatformConfig { + + @Bean + public String foo() { + return "foo"; + } + + } + + @Configuration + static class CloudFoundryPlatformOnMethodConfig { + + @Bean + @ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY) + public String foo() { + return "foo"; + } + + } + +} From 7f1ff968a117df4eaa737e862e572838d3c7face Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Tue, 11 Oct 2016 15:00:58 -0700 Subject: [PATCH 4/6] Support NamedMvcEndpoints Introduce a new NamedMvcEndpoint interface which can be used when an MvcEndpoint also has a logical name. Existing MvcEndpoints have been reworked to implement the NamedMvcEndpoint interface. Fixes gh-7156 --- .../mvc/AbstractEndpointMvcAdapter.java | 7 ++- .../mvc/AbstractNamedMvcEndpoint.java | 52 +++++++++++++++++++ .../actuate/endpoint/mvc/DocsMvcEndpoint.java | 4 +- .../endpoint/mvc/HalJsonMvcEndpoint.java | 4 +- .../endpoint/mvc/HeapdumpMvcEndpoint.java | 4 +- .../endpoint/mvc/JolokiaMvcEndpoint.java | 4 +- .../endpoint/mvc/LogFileMvcEndpoint.java | 4 +- .../actuate/endpoint/mvc/MvcEndpoint.java | 1 + .../endpoint/mvc/NamedMvcEndpoint.java | 37 +++++++++++++ 9 files changed, 106 insertions(+), 11 deletions(-) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractNamedMvcEndpoint.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/NamedMvcEndpoint.java diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointMvcAdapter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointMvcAdapter.java index a90587d58f8..1415a63e282 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointMvcAdapter.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractEndpointMvcAdapter.java @@ -30,7 +30,7 @@ import org.springframework.util.Assert; * @since 1.3.0 */ public abstract class AbstractEndpointMvcAdapter> - implements MvcEndpoint { + implements NamedMvcEndpoint { private final E delegate; @@ -60,6 +60,11 @@ public abstract class AbstractEndpointMvcAdapter> return this.delegate; } + @Override + public String getName() { + return this.delegate.getId(); + } + @Override public String getPath() { return (this.path != null ? this.path : "/" + this.delegate.getId()); diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractNamedMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractNamedMvcEndpoint.java new file mode 100644 index 00000000000..2c791133574 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/AbstractNamedMvcEndpoint.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2016 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.mvc; + +import org.springframework.boot.actuate.endpoint.Endpoint; +import org.springframework.util.Assert; + +/** + * Abstract base class for {@link NamedMvcEndpoint} implementations without a backing + * {@link Endpoint}. + * + * @author Madhura Bhave + * @since 1.5.0 + */ +public class AbstractNamedMvcEndpoint extends AbstractMvcEndpoint + implements NamedMvcEndpoint { + + private final String name; + + public AbstractNamedMvcEndpoint(String name, String path, boolean sensitive) { + super(path, sensitive); + Assert.hasLength(name, "Name must not be empty"); + this.name = name; + } + + public AbstractNamedMvcEndpoint(String name, String path, boolean sensitive, + boolean enabled) { + super(path, sensitive, enabled); + Assert.hasLength(name, "Name must not be empty"); + this.name = name; + } + + @Override + public String getName() { + return this.name; + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/DocsMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/DocsMvcEndpoint.java index 06b009d1ccf..2d8261b161f 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/DocsMvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/DocsMvcEndpoint.java @@ -28,7 +28,7 @@ import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry * @since 1.3.0 */ @ConfigurationProperties("endpoints.docs") -public class DocsMvcEndpoint extends AbstractMvcEndpoint { +public class DocsMvcEndpoint extends AbstractNamedMvcEndpoint { private static final String DOCS_LOCATION = "classpath:/META-INF/resources/spring-boot-actuator/docs/"; @@ -41,7 +41,7 @@ public class DocsMvcEndpoint extends AbstractMvcEndpoint { } public DocsMvcEndpoint(ManagementServletContext managementServletContext) { - super("/docs", false); + super("docs", "/docs", false); this.managementServletContext = managementServletContext; } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HalJsonMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HalJsonMvcEndpoint.java index ec0b46185da..bb1bcd4648d 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HalJsonMvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HalJsonMvcEndpoint.java @@ -32,12 +32,12 @@ import org.springframework.web.bind.annotation.ResponseBody; * @since 1.3.0 */ @ConfigurationProperties("endpoints.actuator") -public class HalJsonMvcEndpoint extends AbstractMvcEndpoint { +public class HalJsonMvcEndpoint extends AbstractNamedMvcEndpoint { private final ManagementServletContext managementServletContext; public HalJsonMvcEndpoint(ManagementServletContext managementServletContext) { - super(getDefaultPath(managementServletContext), false); + super("actuator", getDefaultPath(managementServletContext), false); this.managementServletContext = managementServletContext; } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpoint.java index f0ca61e6986..210ed279abb 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpoint.java @@ -56,7 +56,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; */ @ConfigurationProperties("endpoints.heapdump") @HypermediaDisabled -public class HeapdumpMvcEndpoint extends AbstractMvcEndpoint { +public class HeapdumpMvcEndpoint extends AbstractNamedMvcEndpoint { private final long timeout; @@ -69,7 +69,7 @@ public class HeapdumpMvcEndpoint extends AbstractMvcEndpoint { } protected HeapdumpMvcEndpoint(long timeout) { - super("/heapdump", true); + super("heapdump", "/heapdump", true); this.timeout = timeout; } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/JolokiaMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/JolokiaMvcEndpoint.java index 0ab3dd7b59b..811d8e5cac5 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/JolokiaMvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/JolokiaMvcEndpoint.java @@ -44,12 +44,12 @@ import org.springframework.web.util.UrlPathHelper; */ @ConfigurationProperties(prefix = "endpoints.jolokia", ignoreUnknownFields = false) @HypermediaDisabled -public class JolokiaMvcEndpoint extends AbstractMvcEndpoint +public class JolokiaMvcEndpoint extends AbstractNamedMvcEndpoint implements InitializingBean, ApplicationContextAware, ServletContextAware { private final ServletWrappingController controller = new ServletWrappingController(); public JolokiaMvcEndpoint() { - super("/jolokia", true); + super("jolokia", "/jolokia", true); this.controller.setServletClass(AgentServlet.class); this.controller.setServletName("jolokia"); } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LogFileMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LogFileMvcEndpoint.java index b25a55f5521..95738bac5fb 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LogFileMvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LogFileMvcEndpoint.java @@ -46,7 +46,7 @@ import org.springframework.web.servlet.resource.ResourceHttpRequestHandler; * @since 1.3.0 */ @ConfigurationProperties(prefix = "endpoints.logfile") -public class LogFileMvcEndpoint extends AbstractMvcEndpoint { +public class LogFileMvcEndpoint extends AbstractNamedMvcEndpoint { private static final Log logger = LogFactory.getLog(LogFileMvcEndpoint.class); @@ -57,7 +57,7 @@ public class LogFileMvcEndpoint extends AbstractMvcEndpoint { private File externalFile; public LogFileMvcEndpoint() { - super("/logfile", true); + super("logfile", "/logfile", true); } public File getExternalFile() { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoint.java index 2bb30de71c1..4255a8e0f10 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoint.java @@ -31,6 +31,7 @@ import org.springframework.http.ResponseEntity; * {@link EndpointHandlerMapping}). * * @author Dave Syer + * @see NamedMvcEndpoint */ public interface MvcEndpoint { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/NamedMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/NamedMvcEndpoint.java new file mode 100644 index 00000000000..87e9a591881 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/NamedMvcEndpoint.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2016 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.mvc; + +/** + * A {@link MvcEndpoint} that also includes a logical name. Unlike {@link #getPath() + * endpoints paths}, it should not be possible for a user to change the endpoint name. + * Names provide a consistent way to reference an endpoint, for example they may be used + * as the {@literal 'rel'} attribute in a HAL response. + * + * @author Madhura Bhave + * @since 1.5.0 + */ +public interface NamedMvcEndpoint extends MvcEndpoint { + + /** + * Return the logical name of the endpoint. Names should be non-null, non-empty, + * alpha-numeric values. + * @return the logical name of the endpoint + */ + String getName(); + +} From 0be8a3027659504c20811f80532231c57d655821 Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Tue, 11 Oct 2016 14:40:18 -0700 Subject: [PATCH 5/6] Add EndpointHandlerMapping.getEndpoints(Class) Add an additional method to EndpointHandlerMapping which allows endpoints of a specific type to be returned. See gh-7108 --- .../endpoint/mvc/EndpointHandlerMapping.java | 23 ++++++++++++++++++- .../mvc/EndpointHandlerMappingTests.java | 9 ++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java index 16d194e466e..6f260344351 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java @@ -19,6 +19,7 @@ package org.springframework.boot.actuate.endpoint.mvc; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -196,9 +197,29 @@ public class EndpointHandlerMapping extends RequestMappingHandlerMapping { /** * Return the endpoints. * @return the endpoints + * @see #getEndpoints(Class) */ public Set getEndpoints() { - return new HashSet(this.endpoints); + return getEndpoints(MvcEndpoint.class); + } + + /** + * Return the endpoints of the specified type. + * @param the endpoint type + * @param type the endpoint type + * @return the endpoints + * @see #getEndpoints() + * @since 1.5.0 + */ + @SuppressWarnings("unchecked") + public Set getEndpoints(Class type) { + Set result = new HashSet(this.endpoints.size()); + for (MvcEndpoint candidate : this.endpoints) { + if (type.isInstance(candidate)) { + result.add((E) candidate); + } + } + return Collections.unmodifiableSet(result); } @Override diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingTests.java index 77c8dc1336e..981affd1d54 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingTests.java @@ -136,6 +136,15 @@ public class EndpointHandlerMappingTests { assertThat(mapping.getHandler(request("POST", "/a"))).isNull(); } + @Test + public void getEndpointsForSpecifiedType() throws Exception { + TestMvcEndpoint endpoint = new TestMvcEndpoint(new TestEndpoint("a")); + TestActionEndpoint other = new TestActionEndpoint(new TestEndpoint("b")); + EndpointHandlerMapping mapping = new EndpointHandlerMapping( + Arrays.asList(endpoint, other)); + assertThat(mapping.getEndpoints(TestMvcEndpoint.class)).containsExactly(endpoint); + } + private MockHttpServletRequest request(String method, String requestURI) { return new MockHttpServletRequest(method, requestURI); } From 7352d8e303e93b423b96d5bb92296f1da483808f Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Tue, 11 Oct 2016 14:37:55 -0700 Subject: [PATCH 6/6] Improve EndpointHandlerMapping subclassing support Update EndpointHandlerMapping so that it can be subclasses easily. Subclasses can override the `path` that is used to map the endpoint, allowing different mapping strategies to be used. See gh-7108 --- .../endpoint/mvc/EndpointHandlerMapping.java | 48 ++++++++++++------- .../mvc/EndpointHandlerMappingTests.java | 30 ++++++++++++ 2 files changed, 60 insertions(+), 18 deletions(-) diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java index 6f260344351..72ca350da95 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java @@ -27,6 +27,7 @@ import java.util.Set; import org.springframework.boot.actuate.endpoint.Endpoint; import org.springframework.context.ApplicationContext; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.servlet.HandlerMapping; @@ -114,31 +115,42 @@ public class EndpointHandlerMapping extends RequestMappingHandlerMapping { return; } String[] patterns = getPatterns(handler, mapping); - super.registerHandlerMethod(handler, method, withNewPatterns(mapping, patterns)); + if (!ObjectUtils.isEmpty(patterns)) { + super.registerHandlerMethod(handler, method, + withNewPatterns(mapping, patterns)); + } } private String[] getPatterns(Object handler, RequestMappingInfo mapping) { - String path = getPath(handler); - String prefix = StringUtils.hasText(this.prefix) ? this.prefix + path : path; - Set defaultPatterns = mapping.getPatternsCondition().getPatterns(); - if (defaultPatterns.isEmpty()) { - return new String[] { prefix, prefix + ".json" }; - } - List patterns = new ArrayList(defaultPatterns); - for (int i = 0; i < patterns.size(); i++) { - patterns.set(i, prefix + patterns.get(i)); - } - return patterns.toArray(new String[patterns.size()]); - } - - private String getPath(Object handler) { if (handler instanceof String) { handler = getApplicationContext().getBean((String) handler); } - if (handler instanceof MvcEndpoint) { - return ((MvcEndpoint) handler).getPath(); + Assert.state(handler instanceof MvcEndpoint, "Only MvcEndpoints are supported"); + String path = getPath((MvcEndpoint) handler); + return (path == null ? null : getEndpointPatterns(path, mapping)); + } + + /** + * Return the path that should be used to map the given {@link MvcEndpoint}. + * @param endpoint the endpoint to map + * @return the path to use for the endpoint or {@code null} if no mapping is required + */ + protected String getPath(MvcEndpoint endpoint) { + return endpoint.getPath(); + } + + private String[] getEndpointPatterns(String path, RequestMappingInfo mapping) { + String patternPrefix = StringUtils.hasText(this.prefix) ? this.prefix + path + : path; + Set defaultPatterns = mapping.getPatternsCondition().getPatterns(); + if (defaultPatterns.isEmpty()) { + return new String[] { patternPrefix, patternPrefix + ".json" }; } - return ""; + List patterns = new ArrayList(defaultPatterns); + for (int i = 0; i < patterns.size(); i++) { + patterns.set(i, patternPrefix + patterns.get(i)); + } + return patterns.toArray(new String[patterns.size()]); } private RequestMappingInfo withNewPatterns(RequestMappingInfo mapping, diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingTests.java index 981affd1d54..8d30dc7301d 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingTests.java @@ -18,6 +18,7 @@ package org.springframework.boot.actuate.endpoint.mvc; import java.lang.reflect.Method; import java.util.Arrays; +import java.util.Collection; import org.junit.Before; import org.junit.Test; @@ -145,6 +146,19 @@ public class EndpointHandlerMappingTests { assertThat(mapping.getEndpoints(TestMvcEndpoint.class)).containsExactly(endpoint); } + @Test + public void pathNotMappedWhenGetPathReturnsNull() throws Exception { + TestMvcEndpoint endpoint = new TestMvcEndpoint(new TestEndpoint("a")); + TestActionEndpoint other = new TestActionEndpoint(new TestEndpoint("b")); + EndpointHandlerMapping mapping = new TestEndpointHandlerMapping( + Arrays.asList(endpoint, other)); + mapping.setApplicationContext(this.context); + mapping.afterPropertiesSet(); + assertThat(mapping.getHandlerMethods()).hasSize(1); + assertThat(mapping.getHandler(request("GET", "/a"))).isNull(); + assertThat(mapping.getHandler(request("POST", "/b"))).isNotNull(); + } + private MockHttpServletRequest request(String method, String requestURI) { return new MockHttpServletRequest(method, requestURI); } @@ -184,4 +198,20 @@ public class EndpointHandlerMappingTests { } + static class TestEndpointHandlerMapping extends EndpointHandlerMapping { + + TestEndpointHandlerMapping(Collection endpoints) { + super(endpoints); + } + + @Override + protected String getPath(MvcEndpoint endpoint) { + if (endpoint instanceof TestActionEndpoint) { + return super.getPath(endpoint); + } + return null; + } + + } + }