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 154d7e9fb1..d4752b5441 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); 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 a90587d58f..1415a63e28 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 0000000000..2c79113357 --- /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 06b009d1cc..2d8261b161 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/EndpointHandlerMapping.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java index 16d194e466..72ca350da9 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; @@ -26,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; @@ -113,33 +115,44 @@ 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; + if (handler instanceof String) { + handler = getApplicationContext().getBean((String) handler); + } + 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[] { prefix, prefix + ".json" }; + return new String[] { patternPrefix, patternPrefix + ".json" }; } List patterns = new ArrayList(defaultPatterns); for (int i = 0; i < patterns.size(); i++) { - patterns.set(i, prefix + patterns.get(i)); + patterns.set(i, patternPrefix + 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(); - } - return ""; - } - private RequestMappingInfo withNewPatterns(RequestMappingInfo mapping, String[] patternStrings) { PatternsRequestCondition patterns = new PatternsRequestCondition(patternStrings, @@ -196,9 +209,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/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 ec0b46185d..bb1bcd4648 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 fcb7afac8d..eb4b1b1cd2 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 @@ -55,7 +55,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; @@ -68,7 +68,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 0ab3dd7b59..811d8e5cac 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 f219cb01cd..cbadb42e59 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 2bb30de71c..4255a8e0f1 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 0000000000..87e9a59188 --- /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(); + +} 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 8e162f97f4..8d30dc7301 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; @@ -41,6 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat; public class EndpointHandlerMappingTests { private final StaticApplicationContext context = new StaticApplicationContext(); + private Method method; @Before @@ -50,8 +52,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 +67,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 +83,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 +94,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 +104,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 +115,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 +126,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); @@ -135,14 +137,36 @@ 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); + } + + @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); } private static class TestEndpoint extends AbstractEndpoint { - TestEndpoint(String path) { - super(path); + TestEndpoint(String id) { + super(id); } @Override @@ -174,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; + } + + } + } 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 0000000000..e0c367752b --- /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 0000000000..4a704254a4 --- /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 0000000000..f2de5088d2 --- /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"; + } + + } + +}