diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ConditionalOnEnabledInfoContributor.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ConditionalOnEnabledInfoContributor.java new file mode 100644 index 00000000000..af8b47e4010 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ConditionalOnEnabledInfoContributor.java @@ -0,0 +1,49 @@ +/* + * 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.autoconfigure; + +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.context.annotation.Conditional; + +/** + * {@link Conditional} that checks whether or not a default info contributor is enabled. + * Matches if the value of the {@code management.info..enabled} property is + * {@code true}. Otherwise, matches if the value of the + * {@code management.info.defaults.enabled} property is {@code true} or if it is not + * configured. + * + * @author Stephane Nicoll + * @since 1.4.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Documented +@Conditional(OnEnabledInfoContributorCondition.class) +public @interface ConditionalOnEnabledInfoContributor { + + /** + * The name of the info contributor. + * @return the name of the info contributor + */ + String value(); + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java index 52d5092de21..8661b8e93e6 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java @@ -20,7 +20,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -47,6 +46,7 @@ import org.springframework.boot.actuate.endpoint.TraceEndpoint; import org.springframework.boot.actuate.health.HealthAggregator; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.OrderedHealthAggregator; +import org.springframework.boot.actuate.info.InfoContributor; import org.springframework.boot.actuate.trace.InMemoryTraceRepository; import org.springframework.boot.actuate.trace.TraceRepository; import org.springframework.boot.autoconfigure.AutoConfigureAfter; @@ -57,15 +57,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; -import org.springframework.boot.autoconfigure.info.GitInfo; import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; -import org.springframework.boot.bind.PropertiesConfigurationFactory; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.AnnotationAwareOrderComparator; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.StandardEnvironment; import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping; /** @@ -78,24 +74,23 @@ import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping; * @author Christian Dupuis * @author Stephane Nicoll * @author Eddú Meléndez + * @author Meang Akira Tanaka + * */ @Configuration -@AutoConfigureAfter({ FlywayAutoConfiguration.class, LiquibaseAutoConfiguration.class }) +@AutoConfigureAfter({FlywayAutoConfiguration.class, LiquibaseAutoConfiguration.class}) @EnableConfigurationProperties(EndpointProperties.class) public class EndpointAutoConfiguration { - @Autowired - private InfoPropertiesConfiguration properties; - - @Autowired(required = false) - private GitInfo gitInfo; - @Autowired(required = false) private HealthAggregator healthAggregator = new OrderedHealthAggregator(); @Autowired(required = false) private Map healthIndicators = new HashMap(); + @Autowired(required = false) + private List infoContributors = new ArrayList(); + @Autowired(required = false) private Collection publicMetrics; @@ -123,12 +118,7 @@ public class EndpointAutoConfiguration { @Bean @ConditionalOnMissingBean public InfoEndpoint infoEndpoint() throws Exception { - LinkedHashMap info = new LinkedHashMap(); - info.putAll(this.properties.infoMap()); - if (this.gitInfo != null && this.gitInfo.getBranch() != null) { - info.put("git", this.gitInfo); - } - return new InfoEndpoint(info); + return new InfoEndpoint(this.infoContributors); } @Bean @@ -212,20 +202,4 @@ public class EndpointAutoConfiguration { } - @Configuration - protected static class InfoPropertiesConfiguration { - - @Autowired - private final ConfigurableEnvironment environment = new StandardEnvironment(); - - public Map infoMap() throws Exception { - PropertiesConfigurationFactory> factory = new PropertiesConfigurationFactory>( - new LinkedHashMap()); - factory.setTargetName("info"); - factory.setPropertySources(this.environment.getPropertySources()); - return factory.getObject(); - } - - } - } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/InfoContributorAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/InfoContributorAutoConfiguration.java new file mode 100644 index 00000000000..96f7fe7d6b0 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/InfoContributorAutoConfiguration.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-2015 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; + +import java.io.IOException; + +import org.springframework.boot.actuate.info.EnvironmentInfoContributor; +import org.springframework.boot.actuate.info.InfoContributor; +import org.springframework.boot.actuate.info.SimpleInfoContributor; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; +import org.springframework.boot.autoconfigure.info.GitInfo; +import org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.core.env.ConfigurableEnvironment; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for standard {@link InfoContributor}s. + * + * @author Meang Akira Tanaka + * @author Stephane Nicoll + * @since 1.4.0 + */ +@Configuration +@AutoConfigureAfter(ProjectInfoAutoConfiguration.class) +@AutoConfigureBefore(EndpointAutoConfiguration.class) +public class InfoContributorAutoConfiguration { + + /** + * The default order for the core {@link InfoContributor} beans. + */ + public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 10; + + @Bean + @ConditionalOnEnabledInfoContributor("env") + @Order(DEFAULT_ORDER) + public EnvironmentInfoContributor envInfoContributor(ConfigurableEnvironment environment) { + return new EnvironmentInfoContributor(environment); + } + + @Bean + @ConditionalOnEnabledInfoContributor("git") + @ConditionalOnSingleCandidate(GitInfo.class) + @Order(DEFAULT_ORDER) + public InfoContributor gitInfoContributor(GitInfo gitInfo) throws IOException { + return new SimpleInfoContributor("git", gitInfo); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/OnEnabledEndpointElementCondition.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/OnEnabledEndpointElementCondition.java new file mode 100644 index 00000000000..ae5454d511b --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/OnEnabledEndpointElementCondition.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.actuate.autoconfigure; + +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.bind.RelaxedPropertyResolver; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * Base endpoint element condition. An element can be disabled globally via the + * `defaults` name or individually via the name of the element. + * + * @author Stephane Nicoll + */ +abstract class OnEnabledEndpointElementCondition extends SpringBootCondition { + + private final String prefix; + + private final Class annotationType; + + OnEnabledEndpointElementCondition(String prefix, Class annotationType) { + this.prefix = prefix; + this.annotationType = annotationType; + } + + protected String getEndpointElementOutcomeMessage(String name, boolean match) { + return "The endpoint element " + name + " is " + (match ? "enabled" : "disabled"); + } + + protected String getDefaultEndpointElementOutcomeMessage(boolean match) { + return "All default endpoint elements are " + (match ? "enabled" : "disabled") + + " by default"; + } + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, + AnnotatedTypeMetadata metadata) { + AnnotationAttributes annotationAttributes = AnnotationAttributes + .fromMap(metadata.getAnnotationAttributes(this.annotationType.getName())); + String endpointName = annotationAttributes.getString("value"); + ConditionOutcome outcome = getEndpointOutcome(context, endpointName); + if (outcome != null) { + return outcome; + } + return getDefaultEndpointsOutcome(context); + } + + protected ConditionOutcome getEndpointOutcome(ConditionContext context, + String endpointName) { + RelaxedPropertyResolver resolver = new RelaxedPropertyResolver( + context.getEnvironment(), this.prefix + endpointName + "."); + if (resolver.containsProperty("enabled")) { + boolean match = resolver.getProperty("enabled", Boolean.class, true); + return new ConditionOutcome(match, + getEndpointElementOutcomeMessage(endpointName, match)); + } + return null; + } + + protected ConditionOutcome getDefaultEndpointsOutcome(ConditionContext context) { + RelaxedPropertyResolver resolver = new RelaxedPropertyResolver( + context.getEnvironment(), this.prefix + "defaults."); + boolean match = Boolean.valueOf(resolver.getProperty("enabled", "true")); + return new ConditionOutcome(match, getDefaultEndpointElementOutcomeMessage(match)); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/OnEnabledHealthIndicatorCondition.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/OnEnabledHealthIndicatorCondition.java index 4cbb0a369d2..35bceaf1e49 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/OnEnabledHealthIndicatorCondition.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/OnEnabledHealthIndicatorCondition.java @@ -16,55 +16,28 @@ package org.springframework.boot.actuate.autoconfigure; -import org.springframework.boot.autoconfigure.condition.ConditionOutcome; -import org.springframework.boot.autoconfigure.condition.SpringBootCondition; -import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.context.annotation.Condition; -import org.springframework.context.annotation.ConditionContext; -import org.springframework.core.annotation.AnnotationAttributes; -import org.springframework.core.type.AnnotatedTypeMetadata; /** * {@link Condition} that checks if a health indicator is enabled. * * @author Stephane Nicoll */ -class OnEnabledHealthIndicatorCondition extends SpringBootCondition { +class OnEnabledHealthIndicatorCondition extends OnEnabledEndpointElementCondition { - private static final String ANNOTATION_CLASS = ConditionalOnEnabledHealthIndicator.class - .getName(); - - @Override - public ConditionOutcome getMatchOutcome(ConditionContext context, - AnnotatedTypeMetadata metadata) { - AnnotationAttributes annotationAttributes = AnnotationAttributes - .fromMap(metadata.getAnnotationAttributes(ANNOTATION_CLASS)); - String endpointName = annotationAttributes.getString("value"); - ConditionOutcome outcome = getHealthIndicatorOutcome(context, endpointName); - if (outcome != null) { - return outcome; - } - return getDefaultIndicatorsOutcome(context); + OnEnabledHealthIndicatorCondition() { + super("management.health.", ConditionalOnEnabledHealthIndicator.class); } - private ConditionOutcome getHealthIndicatorOutcome(ConditionContext context, - String endpointName) { - RelaxedPropertyResolver resolver = new RelaxedPropertyResolver( - context.getEnvironment(), "management.health." + endpointName + "."); - if (resolver.containsProperty("enabled")) { - boolean match = resolver.getProperty("enabled", Boolean.class, true); - return new ConditionOutcome(match, "The health indicator " + endpointName - + " is " + (match ? "enabled" : "disabled")); - } - return null; + @Override + protected String getEndpointElementOutcomeMessage(String name, boolean match) { + return "The health indicator " + name + " is " + (match ? "enabled" : "disabled"); } - private ConditionOutcome getDefaultIndicatorsOutcome(ConditionContext context) { - RelaxedPropertyResolver resolver = new RelaxedPropertyResolver( - context.getEnvironment(), "management.health.defaults."); - boolean match = Boolean.valueOf(resolver.getProperty("enabled", "true")); - return new ConditionOutcome(match, "All default health indicators are " - + (match ? "enabled" : "disabled") + " by default"); + @Override + protected String getDefaultEndpointElementOutcomeMessage(boolean match) { + return "All default health indicators are " + (match ? "enabled" : "disabled") + + " by default"; } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/OnEnabledInfoContributorCondition.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/OnEnabledInfoContributorCondition.java new file mode 100644 index 00000000000..a591ce25a03 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/OnEnabledInfoContributorCondition.java @@ -0,0 +1,42 @@ +/* + * 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.autoconfigure; + +import org.springframework.context.annotation.Condition; + +/** + * {@link Condition} that checks if a info indicator is enabled. + * + * @author Stephane Nicoll + */ +class OnEnabledInfoContributorCondition extends OnEnabledEndpointElementCondition { + + OnEnabledInfoContributorCondition() { + super("management.info.", ConditionalOnEnabledInfoContributor.class); + } + + @Override + protected String getEndpointElementOutcomeMessage(String name, boolean match) { + return "The info contributor " + name + " is " + (match ? "enabled" : "disabled"); + } + + @Override + protected String getDefaultEndpointElementOutcomeMessage(boolean match) { + return "All default info contributors are " + (match ? "enabled" : "disabled") + + " by default"; + } +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/InfoEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/InfoEndpoint.java index af8396901ca..d41fe6897f8 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/InfoEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/InfoEndpoint.java @@ -17,9 +17,11 @@ package org.springframework.boot.actuate.endpoint; import java.util.Collections; -import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import org.springframework.boot.actuate.info.Info; +import org.springframework.boot.actuate.info.InfoContributor; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.util.Assert; @@ -27,30 +29,41 @@ import org.springframework.util.Assert; * {@link Endpoint} to expose arbitrary application information. * * @author Dave Syer + * @author Meang Akira Tanaka + * @author Stephane Nicoll */ @ConfigurationProperties(prefix = "endpoints.info") -public class InfoEndpoint extends AbstractEndpoint> { +public class InfoEndpoint extends AbstractEndpoint { - private final Map info; + private final List infoContributors; /** * Create a new {@link InfoEndpoint} instance. - * - * @param info the info to expose + * @param infoContributors the info contributors to use */ - public InfoEndpoint(Map info) { + public InfoEndpoint(List infoContributors) { super("info", false); - Assert.notNull(info, "Info must not be null"); - this.info = info; + Assert.notNull(infoContributors, "Info contributors must not be null"); + this.infoContributors = infoContributors; } + @SuppressWarnings("deprecation") @Override - public Map invoke() { - Map info = new LinkedHashMap(this.info); - info.putAll(getAdditionalInfo()); - return info; + public Info invoke() { + Info.Builder builder = new Info.Builder(); + for (InfoContributor contributor : this.infoContributors) { + contributor.contribute(builder); + } + builder.withDetails(getAdditionalInfo()); + return builder.build(); } + /** + * Return additional information to include in the output. + * @return additional information + * @deprecated define an additional {@link InfoContributor} bean instead. + */ + @Deprecated protected Map getAdditionalInfo() { return Collections.emptyMap(); } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/AbstractEnvironmentInfoContributor.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/AbstractEnvironmentInfoContributor.java new file mode 100644 index 00000000000..d529969baf3 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/AbstractEnvironmentInfoContributor.java @@ -0,0 +1,78 @@ +/* + * 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.info; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.boot.bind.PropertiesConfigurationFactory; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; +import org.springframework.validation.BindException; + +/** + * A base {@link InfoContributor} implementation working on the {@link Environment}. + * + * @author Stephane Nicoll + * @since 1.4.0 + */ +public abstract class AbstractEnvironmentInfoContributor implements InfoContributor { + + private final ConfigurableEnvironment environment; + + protected AbstractEnvironmentInfoContributor(ConfigurableEnvironment environment) { + this.environment = environment; + } + + public final ConfigurableEnvironment getEnvironment() { + return this.environment; + } + + + /** + * Extract the keys from the environment using the specified {@code prefix}. The + * prefix won't be included. + *

Any key that starts with the {@code prefix} will be included + * @param prefix the prefix to use + * @return the keys from the environment matching the prefix + */ + protected Map extract(String prefix) { + Map content = new LinkedHashMap(); + bindEnvironmentTo(prefix, content); + return content; + } + + /** + * Bind the specified {@code target} from the environment using the {@code prefix}. + *

Any key that starts with the {@code prefix} will be bound to the {@code target}. + * @param prefix the prefix to use + * @param target the object to bind to + */ + protected void bindEnvironmentTo(String prefix, Object target) { + PropertiesConfigurationFactory factory = + new PropertiesConfigurationFactory(target); + factory.setTargetName(prefix); + factory.setPropertySources(this.environment.getPropertySources()); + try { + factory.bindPropertiesToTarget(); + } + catch (BindException ex) { + throw new IllegalStateException("Cannot bind to " + target, ex); + } + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/EnvironmentInfoContributor.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/EnvironmentInfoContributor.java new file mode 100644 index 00000000000..9d34a4212df --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/EnvironmentInfoContributor.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2015 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.info; + +import java.util.Map; + +import org.springframework.core.env.ConfigurableEnvironment; + +/** + * A {@link InfoContributor} that provides all environment entries prefixed with + * info. + * + * @author Meang Akira Tanaka + * @author Stephane Nicoll + * @since 1.4.0 + */ +public class EnvironmentInfoContributor extends AbstractEnvironmentInfoContributor { + + private final Map info; + + public EnvironmentInfoContributor(ConfigurableEnvironment environment) { + super(environment); + this.info = extract("info"); + } + + @Override + public void contribute(Info.Builder builder) { + builder.withDetails(this.info); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/Info.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/Info.java new file mode 100644 index 00000000000..1895eff4175 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/Info.java @@ -0,0 +1,132 @@ +/* + * Copyright 2012-2015 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.info; + +import java.util.LinkedHashMap; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +/** + * Carries information of the application. + *

+ * Each detail element can singular or a hierarchical object such as a pojo or a nested + * Map. + * + * @author Meang Akira Tanaka + * @author Stephane Nicoll + * @since 1.4.0 + */ +@JsonInclude(Include.NON_EMPTY) +public final class Info { + + private final Map details; + + private Info(Builder builder) { + this.details = new LinkedHashMap(); + this.details.putAll(builder.content); + } + + /** + * Return the content. + * @return the details of the info or an empty map. + */ + @JsonAnyGetter + public Map getDetails() { + return this.details; + } + + public Object get(String id) { + return this.details.get(id); + } + + @SuppressWarnings("unchecked") + public T get(String id, Class type) { + Object value = get(id); + if (value != null && type != null && !type.isInstance(value)) { + throw new IllegalStateException("Info entry is not of required type [" + type.getName() + "]: " + value); + } + return (T) value; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj != null && obj instanceof Info) { + Info other = (Info) obj; + return this.details.equals(other.details); + } + return false; + } + + @Override + public int hashCode() { + return this.details.hashCode(); + } + + @Override + public String toString() { + return getDetails().toString(); + } + + /** + * Builder for creating immutable {@link Info} instances. + */ + public static class Builder { + + private final Map content; + + public Builder() { + this.content = new LinkedHashMap(); + } + + /** + * Record detail using {@code key} and {@code value}. + * @param key the detail key + * @param data the detail data + * @return this {@link Builder} instance + */ + public Builder withDetail(String key, Object data) { + this.content.put(key, data); + return this; + } + + /** + * Record several details. + * @param details the details + * @return this {@link Builder} instance + * @see #withDetail(String, Object) + */ + public Builder withDetails(Map details) { + this.content.putAll(details); + return this; + } + + /** + * Create a new {@link Info} instance base on the state of this builder. + * @return a new {@link Info} instance + */ + public Info build() { + return new Info(this); + } + + } +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/InfoContributor.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/InfoContributor.java new file mode 100644 index 00000000000..f4c7628e755 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/InfoContributor.java @@ -0,0 +1,33 @@ +/* + * 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.info; + +/** + * Contributes additional info details. + * + * @author Stephane Nicoll + * @since 1.4.0 + */ +public interface InfoContributor { + + /** + * Contributes additional details using the specified {@link Info.Builder Builder}. + * @param builder the builder to use + */ + void contribute(Info.Builder builder); + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/SimpleInfoContributor.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/SimpleInfoContributor.java new file mode 100644 index 00000000000..b6bd85cc197 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/SimpleInfoContributor.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.actuate.info; + +import org.springframework.util.Assert; + +/** + * A simple {@link InfoContributor} that exposes a single detail. + * + * @author Stephane Nicoll + * @since 1.4.0 + */ +public class SimpleInfoContributor implements InfoContributor { + + private final String prefix; + + private final Object detail; + + public SimpleInfoContributor(String prefix, Object detail) { + Assert.notNull(prefix, "Prefix must not be null"); + this.prefix = prefix; + this.detail = detail; + } + + @Override + public void contribute(Info.Builder builder) { + if (this.detail != null) { + builder.withDetail(this.prefix, this.detail); + } + } + +} diff --git a/spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json index fd726b13c20..674c2c69da5 100644 --- a/spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -123,6 +123,24 @@ "description": "Enable Mail health check.", "defaultValue": true }, + { + "name": "management.info.defaults.enabled", + "type": "java.lang.Boolean", + "description": "Enable default info contributors.", + "defaultValue": true + }, + { + "name": "management.info.env.enabled", + "type": "java.lang.Boolean", + "description": "Enable environment info.", + "defaultValue": true + }, + { + "name": "management.info.git.enabled", + "type": "java.lang.Boolean", + "description": "Enable git info.", + "defaultValue": true + }, { "name": "spring.git.properties", "type": "java.lang.String", diff --git a/spring-boot-actuator/src/main/resources/META-INF/spring.factories b/spring-boot-actuator/src/main/resources/META-INF/spring.factories index 3874247eadd..41a4f14cede 100644 --- a/spring-boot-actuator/src/main/resources/META-INF/spring.factories +++ b/spring-boot-actuator/src/main/resources/META-INF/spring.factories @@ -6,6 +6,7 @@ org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.EndpointMBeanExportAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.HealthIndicatorAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.InfoContributorAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.JolokiaAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.ManagementWebSecurityAutoConfiguration,\ diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java index 0b89c82090a..0c3eab82612 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java @@ -16,9 +16,12 @@ package org.springframework.boot.actuate.autoconfigure; +import java.io.IOException; import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.Map; +import java.util.Properties; import org.junit.After; import org.junit.Test; @@ -37,16 +40,24 @@ import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint; import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; import org.springframework.boot.actuate.endpoint.TraceEndpoint; import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.info.Info; +import org.springframework.boot.actuate.info.InfoContributor; import org.springframework.boot.actuate.metrics.Metric; import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; import org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration; +import org.springframework.boot.autoconfigure.info.ProjectInfoProperties; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; +import org.springframework.boot.bind.PropertiesConfigurationFactory; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PropertiesLoaderUtils; +import org.springframework.validation.BindException; import static org.assertj.core.api.Assertions.assertThat; @@ -59,6 +70,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Christian Dupuis * @author Stephane Nicoll * @author Eddú Meléndez + * @author Meang Akira Tanaka */ public class EndpointAutoConfigurationTests { @@ -138,11 +150,13 @@ public class EndpointAutoConfigurationTests { } @Test - public void testInfoEndpointConfiguration() throws Exception { + public void testInfoEndpoint() throws Exception { this.context = new AnnotationConfigApplicationContext(); EnvironmentTestUtils.addEnvironment(this.context, "info.foo:bar"); - this.context.register(ProjectInfoAutoConfiguration.class, EndpointAutoConfiguration.class); + this.context.register(ProjectInfoAutoConfiguration.class, + InfoContributorAutoConfiguration.class, EndpointAutoConfiguration.class); this.context.refresh(); + InfoEndpoint endpoint = this.context.getBean(InfoEndpoint.class); assertThat(endpoint).isNotNull(); assertThat(endpoint.invoke().get("git")).isNotNull(); @@ -150,17 +164,34 @@ public class EndpointAutoConfigurationTests { } @Test - public void testNoGitProperties() throws Exception { + public void testInfoEndpointNoGitProperties() throws Exception { this.context = new AnnotationConfigApplicationContext(); EnvironmentTestUtils.addEnvironment(this.context, "spring.info.git.location:classpath:nonexistent"); - this.context.register(EndpointAutoConfiguration.class); + this.context.register(InfoContributorAutoConfiguration.class, EndpointAutoConfiguration.class); this.context.refresh(); InfoEndpoint endpoint = this.context.getBean(InfoEndpoint.class); assertThat(endpoint).isNotNull(); assertThat(endpoint.invoke().get("git")).isNull(); } + @Test + public void testInfoEndpointOrdering() throws Exception { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, "info.name:foo"); + this.context.register(CustomInfoContributorsConfig.class, ProjectInfoAutoConfiguration.class, + InfoContributorAutoConfiguration.class, EndpointAutoConfiguration.class); + this.context.refresh(); + + InfoEndpoint endpoint = this.context.getBean(InfoEndpoint.class); + Info info = endpoint.invoke(); + assertThat(info).isNotNull(); + assertThat(info.get("name")).isEqualTo("foo"); + assertThat(info.get("version")).isEqualTo("1.0"); + Object git = info.get("git"); + assertThat(git).isInstanceOf(Map.class); + } + @Test public void testFlywayEndpoint() { this.context = new AnnotationConfigApplicationContext(); @@ -204,4 +235,53 @@ public class EndpointAutoConfigurationTests { } } + + @Configuration + static class CustomInfoContributorsConfig { + + @Bean + @Order(InfoContributorAutoConfiguration.DEFAULT_ORDER - 1) + public InfoContributor myInfoContributor() { + return new InfoContributor() { + @Override + public void contribute(Info.Builder builder) { + builder.withDetail("name", "bar"); + builder.withDetail("version", "1.0"); + } + }; + } + + @Bean + @Order(InfoContributorAutoConfiguration.DEFAULT_ORDER + 1) + public InfoContributor myAnotherContributor(ProjectInfoProperties properties) + throws IOException, BindException { + return new GitFullInfoContributor(properties.getGit().getLocation()); + } + + private static class GitFullInfoContributor implements InfoContributor { + + private final Map content; + + GitFullInfoContributor(Resource location) throws BindException, IOException { + this.content = new LinkedHashMap(); + if (location.exists()) { + PropertiesConfigurationFactory> factory + = new PropertiesConfigurationFactory>(this.content); + factory.setTargetName("git"); + Properties gitInfoProperties = PropertiesLoaderUtils + .loadProperties(location); + factory.setProperties(gitInfoProperties); + factory.bindPropertiesToTarget(); + } + } + + @Override + public void contribute(Info.Builder builder) { + if (!this.content.isEmpty()) { + builder.withDetail("git", this.content); + } + } + } + + } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/InfoContributorAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/InfoContributorAutoConfigurationTests.java new file mode 100644 index 00000000000..0f0c5cfb57e --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/InfoContributorAutoConfigurationTests.java @@ -0,0 +1,125 @@ +/* + * 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.autoconfigure; + +import java.util.Map; + +import org.junit.After; +import org.junit.Test; + +import org.springframework.boot.actuate.info.Info; +import org.springframework.boot.actuate.info.InfoContributor; +import org.springframework.boot.autoconfigure.info.GitInfo; +import org.springframework.boot.test.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 InfoContributorAutoConfiguration}. + * + * @author Stephane Nicoll + */ +public class InfoContributorAutoConfigurationTests { + + private AnnotationConfigApplicationContext context; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void disableEnvContributor() { + load("management.info.env.enabled:false"); + Map beans = this.context + .getBeansOfType(InfoContributor.class); + assertThat(beans).hasSize(0); + } + + @Test + public void defaultInfoContributorsDisabled() { + load("management.info.defaults.enabled:false"); + Map beans = this.context + .getBeansOfType(InfoContributor.class); + assertThat(beans).hasSize(0); + } + + @Test + public void defaultInfoContributorsDisabledWithCustomOne() { + load(CustomInfoProviderConfiguration.class, + "management.info.defaults.enabled:false"); + Map beans = this.context + .getBeansOfType(InfoContributor.class); + assertThat(beans).hasSize(1); + assertThat(this.context.getBean("customInfoContributor")) + .isSameAs(beans.values().iterator().next()); + } + + @Test + public void gitInfoAvailable() { + load(GitInfoConfiguration.class); + Map beans = this.context + .getBeansOfType(InfoContributor.class); + assertThat(beans).containsKeys("gitInfoContributor"); + } + + private void load(String... environment) { + load(null, environment); + } + + private void load(Class config, String... environment) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + if (config != null) { + context.register(config); + } + context.register(InfoContributorAutoConfiguration.class); + EnvironmentTestUtils.addEnvironment(context, environment); + context.refresh(); + this.context = context; + } + + @Configuration + static class GitInfoConfiguration { + + @Bean + public GitInfo gitInfo() { + GitInfo gitInfo = new GitInfo(); + gitInfo.setBranch("master"); + gitInfo.getCommit().setId("abcdefg"); + return gitInfo; + } + } + + @Configuration + static class CustomInfoProviderConfiguration { + + @Bean + public InfoContributor customInfoContributor() { + return new InfoContributor() { + @Override + public void contribute(Info.Builder builder) { + } + }; + } + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/InfoEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/InfoEndpointTests.java index d0a0573a320..29306e73dd1 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/InfoEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/InfoEndpointTests.java @@ -16,10 +16,12 @@ package org.springframework.boot.actuate.endpoint; -import java.util.Collections; +import java.util.List; import org.junit.Test; +import org.springframework.boot.actuate.info.Info; +import org.springframework.boot.actuate.info.InfoContributor; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -31,6 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Phillip Webb * @author Dave Syer + * @author Meang Akira Tanaka */ public class InfoEndpointTests extends AbstractEndpointTests { @@ -40,7 +43,8 @@ public class InfoEndpointTests extends AbstractEndpointTests { @Test public void invoke() throws Exception { - assertThat(getEndpointBean().invoke().get("a")).isEqualTo("b"); + Info actual = getEndpointBean().invoke(); + assertThat(actual.get("key1")).isEqualTo("value1"); } @Configuration @@ -48,9 +52,22 @@ public class InfoEndpointTests extends AbstractEndpointTests { public static class Config { @Bean - public InfoEndpoint endpoint() { - return new InfoEndpoint(Collections.singletonMap("a", "b")); + public InfoContributor infoProvider() { + return new InfoContributor() { + + @Override + public void contribute(Info.Builder builder) { + builder.withDetail("key1", "value1"); + } + + }; + } + + @Bean + public InfoEndpoint endpoint(List infoContributors) { + return new InfoEndpoint(infoContributors); } } + } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointTests.java new file mode 100644 index 00000000000..6cb5d009c36 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointTests.java @@ -0,0 +1,127 @@ +/* + * 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 java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration; +import org.springframework.boot.actuate.endpoint.InfoEndpoint; +import org.springframework.boot.actuate.endpoint.mvc.InfoMvcEndpointTests.TestConfiguration; +import org.springframework.boot.actuate.info.Info; +import org.springframework.boot.actuate.info.InfoContributor; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Tests for {@link InfoMvcEndpointTests} + * + * @author Meang Akira Tanaka + * @author Stephane Nicoll + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = {TestConfiguration.class}) +@WebAppConfiguration +@TestPropertySource(properties = {"info.app.name=MyService"}) +public class InfoMvcEndpointTests { + @Autowired + private WebApplicationContext context; + + private MockMvc mvc; + + @Before + public void setUp() { + + this.context.getBean(InfoEndpoint.class).setEnabled(true); + this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + } + + @Test + public void home() throws Exception { + this.mvc.perform(get("/info")).andExpect(status().isOk()) + .andExpect(content().string( + containsString("\"beanName1\":{\"key11\":\"value11\",\"key12\":\"value12\"}") + )) + .andExpect(content().string( + containsString("\"beanName2\":{\"key21\":\"value21\",\"key22\":\"value22\"}"))); + } + + @Import({JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + EndpointWebMvcAutoConfiguration.class, + WebMvcAutoConfiguration.class, + ManagementServerPropertiesAutoConfiguration.class}) + @Configuration + public static class TestConfiguration { + + @Bean + public InfoEndpoint endpoint() { + return new InfoEndpoint(Arrays.asList(beanName1(), beanName2())); + } + + @Bean + public InfoContributor beanName1() { + return new InfoContributor() { + + @Override + public void contribute(Info.Builder builder) { + Map content = new LinkedHashMap(); + content.put("key11", "value11"); + content.put("key12", "value12"); + builder.withDetail("beanName1", content); + } + }; + } + + @Bean + public InfoContributor beanName2() { + return new InfoContributor() { + @Override + public void contribute(Info.Builder builder) { + Map content = new LinkedHashMap(); + content.put("key21", "value21"); + content.put("key22", "value22"); + builder.withDetail("beanName2", content); + } + }; + } + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointWithoutAnyInfoProvidersTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointWithoutAnyInfoProvidersTests.java new file mode 100644 index 00000000000..8d48d92c357 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/InfoMvcEndpointWithoutAnyInfoProvidersTests.java @@ -0,0 +1,88 @@ +/* + * 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 java.util.Collections; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration; +import org.springframework.boot.actuate.endpoint.InfoEndpoint; +import org.springframework.boot.actuate.endpoint.mvc.InfoMvcEndpointWithoutAnyInfoProvidersTests.TestConfiguration; +import org.springframework.boot.actuate.info.InfoContributor; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Tests for {@link InfoMvcEndpointWithoutAnyInfoProvidersTests} + * + * @author Meang Akira Tanaka + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = {TestConfiguration.class}) +@WebAppConfiguration +public class InfoMvcEndpointWithoutAnyInfoProvidersTests { + @Autowired + private WebApplicationContext context; + + private MockMvc mvc; + + @Before + public void setUp() { + + this.context.getBean(InfoEndpoint.class).setEnabled(true); + this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + } + + @Test + public void home() throws Exception { + this.mvc.perform(get("/info")).andExpect(status().isOk()); + } + + @Import({JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + EndpointWebMvcAutoConfiguration.class, + WebMvcAutoConfiguration.class, + ManagementServerPropertiesAutoConfiguration.class}) + @Configuration + public static class TestConfiguration { + + @Bean + public InfoEndpoint endpoint() { + return new InfoEndpoint(Collections.emptyList()); + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/EnvironmentInfoContributorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/EnvironmentInfoContributorTests.java new file mode 100644 index 00000000000..5b1d460d146 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/EnvironmentInfoContributorTests.java @@ -0,0 +1,60 @@ +/* + * 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.info; + +import org.junit.Test; + +import org.springframework.boot.test.EnvironmentTestUtils; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.StandardEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link EnvironmentInfoContributor} + */ +public class EnvironmentInfoContributorTests { + + private final StandardEnvironment environment = new StandardEnvironment(); + + @Test + public void extractOnlyInfoProperty() { + EnvironmentTestUtils.addEnvironment(this.environment, + "info.app=my app", "info.version=1.0.0", "foo=bar"); + + Info actual = contributeFrom(this.environment); + assertThat(actual.get("app", String.class)).isEqualTo("my app"); + assertThat(actual.get("version", String.class)).isEqualTo("1.0.0"); + assertThat(actual.getDetails().size()).isEqualTo(2); + } + + @Test + public void extractNoEntry() { + EnvironmentTestUtils.addEnvironment(this.environment, "foo=bar"); + + Info actual = contributeFrom(this.environment); + assertThat(actual.getDetails().size()).isEqualTo(0); + } + + private static Info contributeFrom(ConfigurableEnvironment environment) { + EnvironmentInfoContributor contributor = new EnvironmentInfoContributor(environment); + Info.Builder builder = new Info.Builder(); + contributor.contribute(builder); + return builder.build(); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/SimpleInfoContributorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/SimpleInfoContributorTests.java new file mode 100644 index 00000000000..9b75e4f7b32 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/SimpleInfoContributorTests.java @@ -0,0 +1,56 @@ +/* + * 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.info; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SimpleInfoContributor}. + * + * @author Stephane Nicoll + */ +public class SimpleInfoContributorTests { + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + @Test + public void prefixIsMandatory() { + this.thrown.expect(IllegalArgumentException.class); + new SimpleInfoContributor(null, new Object()); + } + + @Test + public void mapSimpleObject() { + Object o = new Object(); + Info info = contributeFrom("test", o); + assertThat(info.get("test")).isSameAs(o); + } + + + private static Info contributeFrom(String prefix, Object detail) { + SimpleInfoContributor contributor = new SimpleInfoContributor(prefix, detail); + Info.Builder builder = new Info.Builder(); + contributor.contribute(builder); + return builder.build(); + } + +} diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 94dd7e18998..7670bbba3fa 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -884,6 +884,11 @@ content into your application; rather pick only the properties that you need. management.health.solr.enabled=true # Enable Solr health check. management.health.status.order=DOWN, OUT_OF_SERVICE, UNKNOWN, UP # Comma-separated list of health statuses in order of severity. + # INFO CONTRIBUTORS + management.info.defaults.enabled=true # Enable default health indicators. + management.info.env.enabled=true # Enable environment info. + management.info.git.enabled=true # Enable git info. + # TRACING (({sc-spring-boot-actuator}/trace/TraceProperties.{sc-ext}[TraceProperties]) management.trace.include=request-headers,response-headers,errors # Items to be included in the trace. diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/main/java/sample/actuator/ExampleInfoContributor.java b/spring-boot-samples/spring-boot-sample-actuator/src/main/java/sample/actuator/ExampleInfoContributor.java new file mode 100644 index 00000000000..4d976d69302 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-actuator/src/main/java/sample/actuator/ExampleInfoContributor.java @@ -0,0 +1,34 @@ +/* + * 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 sample.actuator; + +import java.util.Collections; + +import org.springframework.boot.actuate.info.Info; +import org.springframework.boot.actuate.info.InfoContributor; +import org.springframework.stereotype.Component; + +@Component +public class ExampleInfoContributor implements InfoContributor { + + @Override + public void contribute(Info.Builder builder) { + builder.withDetail("example", + Collections.singletonMap("someKey", "someValue")); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/SampleActuatorApplicationTests.java b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/SampleActuatorApplicationTests.java index 2277acbfc3d..436b564db63 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/SampleActuatorApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/SampleActuatorApplicationTests.java @@ -45,6 +45,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Basic integration tests for service demo application. * * @author Dave Syer + * @author Stephane Nicoll */ @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(SampleActuatorApplication.class) @@ -145,6 +146,8 @@ public class SampleActuatorApplicationTests { assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(entity.getBody()) .contains("\"artifact\":\"spring-boot-sample-actuator\""); + assertThat(entity.getBody()) + .contains("\"someKey\":\"someValue\""); } @Test