diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/AbstractEndpointCondition.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/AbstractEndpointCondition.java new file mode 100644 index 00000000000..276086e709e --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/AbstractEndpointCondition.java @@ -0,0 +1,87 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.endpoint.condition; + +import java.util.Map; + +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.core.type.MethodMetadata; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Base class for {@link Endpoint} related {@link SpringBootCondition} implementations. + * + * @author Stephane Nicoll + * @author Andy Wilkinson + * @author Phillip Webb + */ +abstract class AbstractEndpointCondition extends SpringBootCondition { + + AnnotationAttributes getEndpointAttributes(Class annotationClass, + ConditionContext context, AnnotatedTypeMetadata metadata) { + return getEndpointAttributes(getEndpointType(annotationClass, context, metadata)); + } + + Class getEndpointType(Class annotationClass, ConditionContext context, + AnnotatedTypeMetadata metadata) { + Map attributes = metadata + .getAnnotationAttributes(annotationClass.getName()); + if (attributes != null && attributes.containsKey("endpoint")) { + Class target = (Class) attributes.get("endpoint"); + if (target != Void.class) { + return target; + } + } + Assert.state( + metadata instanceof MethodMetadata + && metadata.isAnnotated(Bean.class.getName()), + "EndpointCondition must be used on @Bean methods when the endpoint is not specified"); + MethodMetadata methodMetadata = (MethodMetadata) metadata; + try { + return ClassUtils.forName(methodMetadata.getReturnTypeName(), + context.getClassLoader()); + } + catch (Throwable ex) { + throw new IllegalStateException("Failed to extract endpoint id for " + + methodMetadata.getDeclaringClassName() + "." + + methodMetadata.getMethodName(), ex); + } + } + + AnnotationAttributes getEndpointAttributes(Class type) { + AnnotationAttributes attributes = AnnotatedElementUtils + .findMergedAnnotationAttributes(type, Endpoint.class, true, true); + if (attributes != null) { + return attributes; + } + attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(type, + EndpointExtension.class, false, true); + Assert.state(attributes != null, + "No endpoint is specified and the return type of the @Bean method is " + + "neither an @Endpoint, nor an @EndpointExtension"); + return getEndpointAttributes(attributes.getClass("endpoint")); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnExposedEndpoint.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnExposedEndpoint.java new file mode 100644 index 00000000000..fff18ff5779 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnExposedEndpoint.java @@ -0,0 +1,115 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.endpoint.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.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension; +import org.springframework.context.annotation.Conditional; +import org.springframework.core.env.Environment; + +/** + * {@link Conditional} that checks whether an endpoint is exposed or not. Matches + * according to the endpoint exposure configuration {@link Environment} properties. This + * is designed as a companion annotation to {@link ConditionalOnEnabledEndpoint}. + *

+ * For a given {@link Endpoint}, the condition will match if: + *

+ * + * When placed on a {@code @Bean} method, the endpoint defaults to the return type of the + * factory method: + * + *
+ * @Configuration
+ * public class MyConfiguration {
+ *
+ *     @ConditionalOnExposedEndpoint
+ *     @Bean
+ *     public MyEndpoint myEndpoint() {
+ *         ...
+ *     }
+ *
+ * }
+ *

+ * It is also possible to use the same mechanism for extensions: + * + *

+ * @Configuration
+ * public class MyConfiguration {
+ *
+ *     @ConditionalOnExposedEndpoint
+ *     @Bean
+ *     public MyEndpointWebExtension myEndpointWebExtension() {
+ *         ...
+ *     }
+ *
+ * }
+ *

+ * In the sample above, {@code MyEndpointWebExtension} will be created if the endpoint is + * enabled as defined by the rules above. {@code MyEndpointWebExtension} must be a regular + * extension that refers to an endpoint, something like: + * + *

+ * @EndpointWebExtension(endpoint = MyEndpoint.class)
+ * public class MyEndpointWebExtension {
+ *
+ * }
+ *

+ * Alternatively, the target endpoint can be manually specified for components that should + * only be created when a given endpoint is enabled: + * + *

+ * @Configuration
+ * public class MyConfiguration {
+ *
+ *     @ConditionalOnExposedEndpoint(endpoint = MyEndpoint.class)
+ *     @Bean
+ *     public MyComponent myComponent() {
+ *         ...
+ *     }
+ *
+ * }
+ * + * @author Brian Clozel + * @since 2.2.0 + * @see Endpoint + * @see ConditionalOnEnabledEndpoint + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Documented +@Conditional(OnExposedEndpointCondition.class) +public @interface ConditionalOnExposedEndpoint { + + /** + * The endpoint type that should be checked. Inferred when the return type of the + * {@code @Bean} method is either an {@link Endpoint} or an {@link EndpointExtension}. + * @return the endpoint type to check + */ + Class endpoint() default Void.class; + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnEnabledEndpointCondition.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnEnabledEndpointCondition.java index 953c93a22af..a80ba2bde95 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnEnabledEndpointCondition.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnEnabledEndpointCondition.java @@ -16,24 +16,15 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.condition; -import java.util.Map; import java.util.Optional; import org.springframework.boot.actuate.endpoint.EndpointId; -import org.springframework.boot.actuate.endpoint.annotation.Endpoint; -import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension; import org.springframework.boot.autoconfigure.condition.ConditionMessage; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; -import org.springframework.boot.autoconfigure.condition.SpringBootCondition; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ConditionContext; -import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotatedTypeMetadata; -import org.springframework.core.type.MethodMetadata; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; import org.springframework.util.ConcurrentReferenceHashMap; /** @@ -44,7 +35,7 @@ import org.springframework.util.ConcurrentReferenceHashMap; * @author Phillip Webb * @see ConditionalOnEnabledEndpoint */ -class OnEnabledEndpointCondition extends SpringBootCondition { +class OnEnabledEndpointCondition extends AbstractEndpointCondition { private static final String ENABLED_BY_DEFAULT_KEY = "management.endpoints.enabled-by-default"; @@ -54,7 +45,8 @@ class OnEnabledEndpointCondition extends SpringBootCondition { public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment environment = context.getEnvironment(); - AnnotationAttributes attributes = getEndpointAttributes(context, metadata); + AnnotationAttributes attributes = getEndpointAttributes( + ConditionalOnEnabledEndpoint.class, context, metadata); EndpointId id = EndpointId.of(attributes.getString("id")); String key = "management.endpoint." + id.toLowerCaseString() + ".enabled"; Boolean userDefinedEnabled = environment.getProperty(key, Boolean.class); @@ -88,49 +80,4 @@ class OnEnabledEndpointCondition extends SpringBootCondition { return enabledByDefault.orElse(null); } - private AnnotationAttributes getEndpointAttributes(ConditionContext context, - AnnotatedTypeMetadata metadata) { - return getEndpointAttributes(getEndpointType(context, metadata)); - } - - private Class getEndpointType(ConditionContext context, - AnnotatedTypeMetadata metadata) { - Map attributes = metadata - .getAnnotationAttributes(ConditionalOnEnabledEndpoint.class.getName()); - if (attributes != null && attributes.containsKey("endpoint")) { - Class target = (Class) attributes.get("endpoint"); - if (target != Void.class) { - return target; - } - } - Assert.state( - metadata instanceof MethodMetadata - && metadata.isAnnotated(Bean.class.getName()), - "OnEnabledEndpointCondition must be used on @Bean methods when the endpoint is not specified"); - MethodMetadata methodMetadata = (MethodMetadata) metadata; - try { - return ClassUtils.forName(methodMetadata.getReturnTypeName(), - context.getClassLoader()); - } - catch (Throwable ex) { - throw new IllegalStateException("Failed to extract endpoint id for " - + methodMetadata.getDeclaringClassName() + "." - + methodMetadata.getMethodName(), ex); - } - } - - protected AnnotationAttributes getEndpointAttributes(Class type) { - AnnotationAttributes attributes = AnnotatedElementUtils - .findMergedAnnotationAttributes(type, Endpoint.class, true, true); - if (attributes != null) { - return attributes; - } - attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(type, - EndpointExtension.class, false, true); - Assert.state(attributes != null, - "No endpoint is specified and the return type of the @Bean method is " - + "neither an @Endpoint, nor an @EndpointExtension"); - return getEndpointAttributes(attributes.getClass("endpoint")); - } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnExposedEndpointCondition.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnExposedEndpointCondition.java new file mode 100644 index 00000000000..38fe0db79f6 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnExposedEndpointCondition.java @@ -0,0 +1,145 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.endpoint.condition; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.springframework.boot.actuate.endpoint.EndpointId; +import org.springframework.boot.autoconfigure.condition.ConditionMessage; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.cloud.CloudPlatform; +import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.env.Environment; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.util.ConcurrentReferenceHashMap; + +/** + * A condition that checks if an endpoint is exposed. + * + * @author Brian Clozel + * @see ConditionalOnExposedEndpoint + */ +class OnExposedEndpointCondition extends AbstractEndpointCondition { + + private static final String JMX_ENABLED_KEY = "spring.jmx.enabled"; + + private static final ConcurrentReferenceHashMap> endpointExposureCache = new ConcurrentReferenceHashMap<>(); + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, + AnnotatedTypeMetadata metadata) { + Environment environment = context.getEnvironment(); + if (CloudPlatform.CLOUD_FOUNDRY.isActive(environment)) { + return new ConditionOutcome(true, + ConditionMessage.forCondition(ConditionalOnExposedEndpoint.class) + .because("application is running on Cloud Foundry")); + } + AnnotationAttributes attributes = getEndpointAttributes( + ConditionalOnExposedEndpoint.class, context, metadata); + EndpointId id = EndpointId.of(attributes.getString("id")); + Set exposureInformations = getExposureInformation( + environment); + for (ExposureInformation exposureInformation : exposureInformations) { + if (exposureInformation.isExposed(id)) { + return new ConditionOutcome(true, + ConditionMessage.forCondition(ConditionalOnExposedEndpoint.class) + .because("marked as exposed by a 'management.endpoints." + + exposureInformation.getPrefix() + + ".exposure' property")); + } + } + return new ConditionOutcome(false, + ConditionMessage.forCondition(ConditionalOnExposedEndpoint.class).because( + "no 'management.endpoints' property marked it as exposed")); + } + + private Set getExposureInformation(Environment environment) { + Set exposureInformations = endpointExposureCache + .get(environment); + if (exposureInformations == null) { + exposureInformations = new HashSet<>(2); + Binder binder = Binder.get(environment); + if (environment.getProperty(JMX_ENABLED_KEY, Boolean.class, false)) { + exposureInformations.add(new ExposureInformation(binder, "jmx", "*")); + } + exposureInformations + .add(new ExposureInformation(binder, "web", "info", "health")); + endpointExposureCache.put(environment, exposureInformations); + } + return exposureInformations; + } + + static class ExposureInformation { + + private final String prefix; + + private final Set include; + + private final Set exclude; + + private final Set exposeDefaults; + + ExposureInformation(Binder binder, String prefix, String... exposeDefaults) { + this.prefix = prefix; + this.include = bind(binder, + "management.endpoints." + prefix + ".exposure.include"); + this.exclude = bind(binder, + "management.endpoints." + prefix + ".exposure.exclude"); + this.exposeDefaults = new HashSet<>(Arrays.asList(exposeDefaults)); + } + + private Set bind(Binder binder, String name) { + List values = binder.bind(name, Bindable.listOf(String.class)) + .orElse(Collections.emptyList()); + Set result = new HashSet<>(values.size()); + for (String value : values) { + result.add("*".equals(value) ? "*" + : EndpointId.fromPropertyValue(value).toLowerCaseString()); + } + return result; + } + + String getPrefix() { + return this.prefix; + } + + boolean isExposed(EndpointId endpointId) { + String id = endpointId.toLowerCaseString(); + if (!this.exclude.isEmpty()) { + if (this.exclude.contains("*") || this.exclude.contains(id)) { + return false; + } + } + if (this.include.isEmpty()) { + if (this.exposeDefaults.contains("*") + || this.exposeDefaults.contains(id)) { + return true; + } + } + return this.include.contains("*") || this.include.contains(id); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnExposedEndpointTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnExposedEndpointTests.java new file mode 100644 index 00000000000..2385d8f3ac0 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnExposedEndpointTests.java @@ -0,0 +1,244 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.endpoint.condition; + +import org.junit.Test; + +import org.springframework.boot.actuate.endpoint.EndpointFilter; +import org.springframework.boot.actuate.endpoint.ExposableEndpoint; +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConditionalOnExposedEndpoint}. + * + * @author Brian Clozel + */ +public class ConditionalOnExposedEndpointTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(AllEndpointsConfiguration.class); + + @Test + public void outcomeShouldMatchDefaults() { + this.contextRunner.run((context) -> assertThat(context).hasBean("info") + .hasBean("health").doesNotHaveBean("spring").doesNotHaveBean("test")); + } + + @Test + public void outcomeWhenIncludeAllWebShouldMatch() { + this.contextRunner + .withPropertyValues("management.endpoints.web.exposure.include=*") + .run((context) -> assertThat(context).hasBean("info").hasBean("health") + .hasBean("test").hasBean("spring")); + } + + @Test + public void outcomeWhenIncludeAllJmxButJmxDisabledShouldMatchDefaults() { + this.contextRunner + .withPropertyValues("management.endpoints.jmx.exposure.include=*") + .run((context) -> assertThat(context).hasBean("info").hasBean("health") + .doesNotHaveBean("spring").doesNotHaveBean("test")); + } + + @Test + public void outcomeWhenIncludeAllJmxAndJmxEnabledShouldMatch() { + this.contextRunner + .withPropertyValues("management.endpoints.jmx.exposure.include=*", + "spring.jmx.enabled=true") + .run((context) -> assertThat(context).hasBean("info").hasBean("health") + .hasBean("test").hasBean("spring")); + } + + @Test + public void outcomeWhenIncludeAllWebAndExcludeMatchesShouldNotMatch() { + this.contextRunner + .withPropertyValues("management.endpoints.web.exposure.include=*", + "management.endpoints.web.exposure.exclude=spring,info") + .run((context) -> assertThat(context).hasBean("health").hasBean("test") + .doesNotHaveBean("info").doesNotHaveBean("spring")); + } + + @Test + public void outcomeWhenIncludeMatchesAndExcludeMatchesShouldNotMatch() { + this.contextRunner.withPropertyValues( + "management.endpoints.web.exposure.include=info,health,spring,test", + "management.endpoints.web.exposure.exclude=spring,info") + .run((context) -> assertThat(context).hasBean("health").hasBean("test") + .doesNotHaveBean("info").doesNotHaveBean("spring")); + } + + @Test + public void outcomeWhenIncludeMatchesShouldMatch() { + this.contextRunner + .withPropertyValues("management.endpoints.web.exposure.include=spring") + .run((context) -> assertThat(context).hasBean("spring") + .doesNotHaveBean("health").doesNotHaveBean("info") + .doesNotHaveBean("test")); + } + + @Test + public void outcomeWhenIncludeMatchesWithCaseShouldMatch() { + this.contextRunner + .withPropertyValues("management.endpoints.web.exposure.include=sPRing") + .run((context) -> assertThat(context).hasBean("spring") + .doesNotHaveBean("health").doesNotHaveBean("info") + .doesNotHaveBean("test")); + } + + @Test + public void outcomeWhenIncludeMatchesAndExcludeAllShouldNotMatch() { + this.contextRunner.withPropertyValues( + "management.endpoints.web.exposure.include=info,health,spring,test", + "management.endpoints.web.exposure.exclude=*") + .run((context) -> assertThat(context).doesNotHaveBean("health") + .doesNotHaveBean("info").doesNotHaveBean("spring") + .doesNotHaveBean("test")); + } + + @Test + public void outcomeWhenIncludeMatchesShoulMatchWithExtensionsAndComponents() { + this.contextRunner + .withUserConfiguration( + ComponentEnabledIfEndpointIsExposedConfiguration.class) + .withPropertyValues("management.endpoints.web.exposure.include=spring") + .run((context) -> assertThat(context).hasBean("spring") + .hasBean("springComponent").hasBean("springExtension") + .doesNotHaveBean("info").doesNotHaveBean("health") + .doesNotHaveBean("test")); + } + + @Test + public void outcomeWithNoEndpointReferenceShouldFail() { + this.contextRunner + .withUserConfiguration( + ComponentWithNoEndpointReferenceConfiguration.class) + .withPropertyValues("management.endpoints.web.exposure.include=*") + .run((context) -> { + assertThat(context).hasFailed(); + assertThat(context.getStartupFailure().getCause().getMessage()) + .contains( + "No endpoint is specified and the return type of the @Bean method " + + "is neither an @Endpoint, nor an @EndpointExtension"); + }); + } + + @Test + public void outcomeOnCloudFoundryShouldMatchAll() { + this.contextRunner + .withPropertyValues("VCAP_APPLICATION:---") + .run((context) -> assertThat(context).hasBean("info") + .hasBean("health").hasBean("spring").hasBean("test")); + } + + @Endpoint(id = "health") + static class HealthEndpoint { + + } + + @Endpoint(id = "info") + static class InfoEndpoint { + + } + + @Endpoint(id = "spring") + static class SpringEndpoint { + + } + + @Endpoint(id = "test") + static class TestEndpoint { + + } + + @EndpointExtension(endpoint = SpringEndpoint.class, filter = TestFilter.class) + static class SpringEndpointExtension { + + } + + static class TestFilter implements EndpointFilter> { + + @Override + public boolean match(ExposableEndpoint endpoint) { + return true; + } + + } + + @Configuration + static class AllEndpointsConfiguration { + + @Bean + @ConditionalOnExposedEndpoint + public HealthEndpoint health() { + return new HealthEndpoint(); + } + + @Bean + @ConditionalOnExposedEndpoint + public InfoEndpoint info() { + return new InfoEndpoint(); + } + + @Bean + @ConditionalOnExposedEndpoint + public SpringEndpoint spring() { + return new SpringEndpoint(); + } + + @Bean + @ConditionalOnExposedEndpoint + public TestEndpoint test() { + return new TestEndpoint(); + } + + } + + @Configuration + static class ComponentEnabledIfEndpointIsExposedConfiguration { + + @Bean + @ConditionalOnExposedEndpoint(endpoint = SpringEndpoint.class) + public String springComponent() { + return "springComponent"; + } + + @Bean + @ConditionalOnExposedEndpoint + public SpringEndpointExtension springExtension() { + return new SpringEndpointExtension(); + } + + } + + @Configuration + static class ComponentWithNoEndpointReferenceConfiguration { + + @Bean + @ConditionalOnExposedEndpoint + public String springcomp() { + return "springcomp"; + } + + } + +}