This commit is contained in:
Phillip Webb 2015-07-16 12:56:49 -07:00
parent 4405ae4eaf
commit 728e64b929
19 changed files with 136 additions and 113 deletions

View File

@ -177,66 +177,67 @@ public class EndpointWebMvcHypermediaManagementContextConfiguration {
private static class NotSpringDataRestHomePageCondition extends
SpringBootCondition {
private static final String REST_CONFIGURATION_CLASS = "org.springframework."
+ "data.rest.core.config.RepositoryRestConfiguration";
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
if (!ClassUtils
.isPresent(
"org.springframework.data.rest.core.config.RepositoryRestConfiguration",
null)) {
if (!ClassUtils.isPresent(REST_CONFIGURATION_CLASS, null)) {
return ConditionOutcome.match("Spring Data REST is not present");
}
Class<?> type = ClassUtils
.resolveClassName(
"org.springframework.data.rest.core.config.RepositoryRestConfiguration",
null);
return getMatchOutcome(context,
ClassUtils.resolveClassName(REST_CONFIGURATION_CLASS, null));
}
private ConditionOutcome getMatchOutcome(ConditionContext context,
Class<?> configurationClass) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
if (beanFactory.getBeanNamesForType(type, true, false).length == 0) {
if (beanFactory.getBeanNamesForType(configurationClass, true, false).length == 0) {
return ConditionOutcome.match("Spring Data REST is not configured");
}
Environment environment = context.getEnvironment();
String path = getManagementContextPath(beanFactory, environment);
if (isHome(path)) {
path = getProperty(environment, "endpoints.links.", "path");
if (isHome(path)) {
return ConditionOutcome.noMatch("Management context path "
+ "is home and so is links path");
}
return ConditionOutcome.match("Management context path "
+ "is home but links path is not: '" + path + "'");
}
// N.B. we don't cover the case where the user has Spring Data REST
// but changes *its* home page - you'd have to instantiate the
// RepositoryRestConfiguration and look at it's basePath for that.
return ConditionOutcome.match("Management context path "
+ "is not home: '" + path + "'");
}
private String getManagementContextPath(
ConfigurableListableBeanFactory beanFactory, Environment environment) {
String path = getProperty(environment, "management.", "contextPath");
if (path == null
&& hasCustomBeanDefinition(beanFactory,
ManagementServerProperties.class,
ManagementServerPropertiesAutoConfiguration.class)) {
ManagementServerProperties bean = beanFactory
.getBean(ManagementServerProperties.class);
path = bean.getContextPath();
}
if (isHome(path)) {
path = getProperty(environment, "endpoints.links.", "path");
if (isHome(path)) {
return ConditionOutcome
.noMatch("Management context path is home and so is links path");
}
else {
return ConditionOutcome
.match("Management context path is home but links path is not: '"
+ path + "'");
}
}
else {
// N.B. we don't cover the case where the user has Spring Data REST
// but changes *its* home page - you'd have to instantiate the
// RepositoryRestConfiguration and look at it's basePath for that.
return ConditionOutcome
.match("Management context path is not home: '" + path + "'");
path = beanFactory.getBean(ManagementServerProperties.class)
.getContextPath();
}
return path;
}
private static boolean isHome(String path) {
private boolean isHome(String path) {
return path == null || "".equals(path) || "/".equals(path);
}
private static String getProperty(Environment environment, String prefix,
String name) {
private String getProperty(Environment environment, String prefix, String name) {
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(
environment, prefix);
return resolver.getProperty(name, String.class);
}
private static <T> boolean hasCustomBeanDefinition(
private <T> boolean hasCustomBeanDefinition(
ConfigurableListableBeanFactory beanFactory, Class<T> type,
Class<?> configClass) {
String[] names = beanFactory.getBeanNamesForType(type, true, false);

View File

@ -50,7 +50,7 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl
* @author Dave Syer
*/
public class EndpointHandlerMapping extends RequestMappingHandlerMapping implements
ApplicationContextAware {
ApplicationContextAware {
private final Set<MvcEndpoint> endpoints;

View File

@ -16,9 +16,6 @@
package org.springframework.boot.actuate.autoconfigure;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@ -66,6 +63,9 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* Integration tests for MVC {@link Endpoint}s.
*
@ -106,10 +106,10 @@ public class EndpointMvcIntegrationTests {
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ EmbeddedServletContainerAutoConfiguration.class,
ServerPropertiesAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
JacksonAutoConfiguration.class, ErrorMvcAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class })
ServerPropertiesAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
JacksonAutoConfiguration.class, ErrorMvcAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class })
protected static @interface MinimalWebConfiguration {
}
@ -117,8 +117,8 @@ public class EndpointMvcIntegrationTests {
@Configuration
@MinimalWebConfiguration
@Import({ ManagementServerPropertiesAutoConfiguration.class,
JacksonAutoConfiguration.class, EndpointAutoConfiguration.class,
EndpointWebMvcAutoConfiguration.class })
JacksonAutoConfiguration.class, EndpointAutoConfiguration.class,
EndpointWebMvcAutoConfiguration.class })
@RestController
protected static class Application {

View File

@ -154,4 +154,5 @@ public class JacksonProperties {
public void setTimeZone(TimeZone timeZone) {
this.timeZone = timeZone;
}
}

View File

@ -13,6 +13,7 @@ particular, it will not work with Jersey unless you enable Spring MVC as well.
--
[[production-ready-enabling]]
== Enabling production-ready features
The {github-code}/spring-boot-actuator[`spring-boot-actuator`] module provides all of

View File

@ -16,10 +16,6 @@
package sample.hypermedia.jpa;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -36,12 +32,16 @@ import org.springframework.test.web.servlet.MvcResult;
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.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@SpringApplicationConfiguration(classes = SampleHypermediaJpaApplication.class)
@DirtiesContext
@TestPropertySource(properties = { "debug=true", "management.contextPath=",
"endpoints.docs.curies.enabled=false" })
"endpoints.docs.curies.enabled=false" })
public class SampleHypermediaJpaApplicationCustomLinksPathIntegrationTests {
@Autowired
@ -60,15 +60,15 @@ public class SampleHypermediaJpaApplicationCustomLinksPathIntegrationTests {
@Test
public void links() throws Exception {
this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andExpect(jsonPath("$._links").exists())
.andExpect(jsonPath("$._links.health").exists())
.andExpect(jsonPath("$._links.books").exists());
.andExpect(status().isOk()).andExpect(jsonPath("$._links").exists())
.andExpect(jsonPath("$._links.health").exists())
.andExpect(jsonPath("$._links.books").exists());
}
@Test
public void health() throws Exception {
this.mockMvc.perform(get("/health").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andExpect(jsonPath("$._links").exists());
.andExpect(status().isOk()).andExpect(jsonPath("$._links").exists());
}
@Test

View File

@ -16,10 +16,6 @@
package sample.hypermedia.jpa;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -36,12 +32,16 @@ import org.springframework.test.web.servlet.MvcResult;
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.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@SpringApplicationConfiguration(classes = SampleHypermediaJpaApplication.class)
@DirtiesContext
@TestPropertySource(properties = { "endpoints.links.path=/admin", "management.contextPath=",
"endpoints.docs.curies.enabled=false" })
@TestPropertySource(properties = { "endpoints.links.path=/admin",
"management.contextPath=", "endpoints.docs.curies.enabled=false" })
public class SampleHypermediaJpaApplicationSharedRootIntegrationTests {
@Autowired
@ -60,24 +60,24 @@ public class SampleHypermediaJpaApplicationSharedRootIntegrationTests {
@Test
public void home() throws Exception {
this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andExpect(jsonPath("$._links").exists())
.andExpect(jsonPath("$._links.health").exists())
.andExpect(jsonPath("$._links.admin").exists())
.andExpect(jsonPath("$._links.books").exists());
.andExpect(status().isOk()).andExpect(jsonPath("$._links").exists())
.andExpect(jsonPath("$._links.health").exists())
.andExpect(jsonPath("$._links.admin").exists())
.andExpect(jsonPath("$._links.books").exists());
}
@Test
public void health() throws Exception {
this.mockMvc.perform(get("/health").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andExpect(jsonPath("$._links").exists());
.andExpect(status().isOk()).andExpect(jsonPath("$._links").exists());
}
@Test
public void links() throws Exception {
this.mockMvc.perform(get("/admin").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links.health").exists())
.andExpect(jsonPath("$._links").exists());
.andExpect(status().isOk())
.andExpect(jsonPath("$._links.health").exists())
.andExpect(jsonPath("$._links").exists());
}
@Test

View File

@ -22,8 +22,6 @@ import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import sample.hypermedia.jpa.SampleHypermediaJpaApplication;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SampleHypermediaJpaApplication.class)
@WebAppConfiguration

View File

@ -31,7 +31,6 @@ import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import sample.hypermedia.jpa.SampleHypermediaJpaApplication;
import static org.junit.Assert.assertEquals;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

View File

@ -160,7 +160,7 @@ public class ConfigurationMetadataProperty {
* @see #isDeprecated()
*/
public Deprecation getDeprecation() {
return deprecation;
return this.deprecation;
}
public void setDeprecation(Deprecation deprecation) {

View File

@ -34,7 +34,7 @@ public class Deprecation {
* @return the deprecation reason
*/
public String getReason() {
return reason;
return this.reason;
}
public void setReason(String reason) {
@ -42,11 +42,12 @@ public class Deprecation {
}
/**
* The full name of the property that replaces the related deprecated property, if any.
* The full name of the property that replaces the related deprecated property, if
* any.
* @return the replacement property name
*/
public String getReplacement() {
return replacement;
return this.replacement;
}
public void setReplacement(String replacement) {
@ -55,7 +56,8 @@ public class Deprecation {
@Override
public String toString() {
return "Deprecation{" + "reason='" + reason + '\'' + ", replacement='" + replacement + '\'' + '}';
return "Deprecation{" + "reason='" + this.reason + '\'' + ", replacement='"
+ this.replacement + '\'' + '}';
}
}

View File

@ -157,7 +157,8 @@ class JsonReader {
JSONObject deprecationJsonObject = object.getJSONObject("deprecation");
Deprecation deprecation = new Deprecation();
deprecation.setReason(deprecationJsonObject.optString("reason", null));
deprecation.setReplacement(deprecationJsonObject.optString("replacement", null));
deprecation.setReplacement(deprecationJsonObject.optString("replacement",
null));
return deprecation;
}
return (object.optBoolean("deprecated") ? new Deprecation() : null);

View File

@ -141,17 +141,20 @@ public class JsonReaderTests extends AbstractConfigurationMetadataTests {
ConfigurationMetadataItem item = items.get(0);
assertProperty(item, "server.port", "server.port", Integer.class, null);
assertTrue(item.isDeprecated());
assertEquals("Server namespace has moved to spring.server", item.getDeprecation().getReason());
assertEquals("Server namespace has moved to spring.server", item.getDeprecation()
.getReason());
assertEquals("server.spring.port", item.getDeprecation().getReplacement());
ConfigurationMetadataItem item2 = items.get(1);
assertProperty(item2, "server.cluster-name", "server.cluster-name", String.class, null);
assertProperty(item2, "server.cluster-name", "server.cluster-name", String.class,
null);
assertTrue(item2.isDeprecated());
assertEquals(null, item2.getDeprecation().getReason());
assertEquals(null, item2.getDeprecation().getReplacement());
ConfigurationMetadataItem item3 = items.get(2);
assertProperty(item3, "spring.server.name", "spring.server.name", String.class, null);
assertProperty(item3, "spring.server.name", "spring.server.name", String.class,
null);
assertFalse(item3.isDeprecated());
assertEquals(null, item3.getDeprecation());
}

View File

@ -210,10 +210,9 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
boolean deprecated = hasDeprecateAnnotation(getter)
|| hasDeprecateAnnotation(setter)
|| hasDeprecateAnnotation(element);
this.metadataCollector.add(ItemMetadata
.newProperty(prefix, name, dataType, sourceType, null,
description, defaultValue,
deprecated ? new ItemDeprecation() : null));
this.metadataCollector.add(ItemMetadata.newProperty(prefix, name,
dataType, sourceType, null, description, defaultValue,
(deprecated ? new ItemDeprecation() : null)));
}
}
}
@ -240,10 +239,9 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
Object defaultValue = fieldValues.get(name);
boolean deprecated = hasDeprecateAnnotation(field)
|| hasDeprecateAnnotation(element);
this.metadataCollector.add(ItemMetadata
.newProperty(prefix, name, dataType, sourceType, null,
description, defaultValue,
deprecated ? new ItemDeprecation() : null));
this.metadataCollector.add(ItemMetadata.newProperty(prefix, name,
dataType, sourceType, null, description, defaultValue,
(deprecated ? new ItemDeprecation() : null)));
}
}
}

View File

@ -37,7 +37,7 @@ public class ItemDeprecation {
}
public String getReason() {
return reason;
return this.reason;
}
public void setReason(String reason) {
@ -45,7 +45,7 @@ public class ItemDeprecation {
}
public String getReplacement() {
return replacement;
return this.replacement;
}
public void setReplacement(String replacement) {
@ -54,26 +54,42 @@ public class ItemDeprecation {
@Override
public String toString() {
return "ItemDeprecation{" + "reason='" + this.reason + '\'' + ", " +
"replacement='" + this.replacement + '\'' + '}';
return "ItemDeprecation{" + "reason='" + this.reason + '\'' + ", "
+ "replacement='" + this.replacement + '\'' + '}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ItemDeprecation that = (ItemDeprecation) o;
if (reason != null ? !reason.equals(that.reason) : that.reason != null) return false;
return !(replacement != null ? !replacement.equals(that.replacement) : that.replacement != null);
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ItemDeprecation other = (ItemDeprecation) o;
return nullSafeEquals(this.reason, other.reason)
&& nullSafeEquals(this.replacement, other.replacement);
}
@Override
public int hashCode() {
int result = reason != null ? reason.hashCode() : 0;
result = 31 * result + (replacement != null ? replacement.hashCode() : 0);
int result = nullSafeHashCode(this.reason);
result = 31 * result + nullSafeHashCode(this.replacement);
return result;
}
private boolean nullSafeEquals(Object o1, Object o2) {
if (o1 == o2) {
return true;
}
if (o1 == null || o2 == null) {
return false;
}
return o1.equals(o2);
}
private int nullSafeHashCode(Object o) {
return (o == null ? 0 : o.hashCode());
}
}

View File

@ -97,7 +97,7 @@ public class ItemMetadata implements Comparable<ItemMetadata> {
}
public ItemDeprecation getDeprecation() {
return deprecation;
return this.deprecation;
}
@Override

View File

@ -221,7 +221,8 @@ public class JsonMarshaller {
JSONObject deprecationJsonObject = object.getJSONObject("deprecation");
ItemDeprecation deprecation = new ItemDeprecation();
deprecation.setReason(deprecationJsonObject.optString("reason", null));
deprecation.setReplacement(deprecationJsonObject.optString("replacement", null));
deprecation.setReplacement(deprecationJsonObject.optString("replacement",
null));
return deprecation;
}
return (object.optBoolean("deprecated") ? new ItemDeprecation() : null);

View File

@ -416,13 +416,12 @@ public class ConfigurationMetadataAnnotationProcessorTests {
@Test
public void mergingOfAdditionalDeprecation() throws Exception {
writePropertyDeprecation(ItemMetadata.newProperty("simple", "wrongName", "java.lang.String",
null, null, null, null, new ItemDeprecation("Lame name.", "simple.the-name")));
writePropertyDeprecation(ItemMetadata.newProperty("simple", "wrongName",
"java.lang.String", null, null, null, null, new ItemDeprecation(
"Lame name.", "simple.the-name")));
ConfigurationMetadata metadata = compile(SimpleProperties.class);
assertThat(
metadata,
containsProperty("simple.wrong-name", String.class)
.withDeprecation("Lame name.", "simple.the-name"));
assertThat(metadata, containsProperty("simple.wrong-name", String.class)
.withDeprecation("Lame name.", "simple.the-name"));
}
@Test

View File

@ -165,12 +165,14 @@ public class ConfigurationMetadataMatchers {
public ContainsItemMatcher ofType(Class<?> dataType) {
return new ContainsItemMatcher(this.itemType, this.name, dataType.getName(),
this.sourceType, this.description, this.defaultValue, this.deprecation);
this.sourceType, this.description, this.defaultValue,
this.deprecation);
}
public ContainsItemMatcher ofType(String dataType) {
return new ContainsItemMatcher(this.itemType, this.name, dataType,
this.sourceType, this.description, this.defaultValue, this.deprecation);
this.sourceType, this.description, this.defaultValue,
this.deprecation);
}
public ContainsItemMatcher fromSource(Class<?> sourceType) {
@ -190,7 +192,8 @@ public class ConfigurationMetadataMatchers {
public ContainsItemMatcher withDeprecation(String reason, String replacement) {
return new ContainsItemMatcher(this.itemType, this.name, this.type,
this.sourceType, this.description, this.defaultValue, new ItemDeprecation(reason, replacement));
this.sourceType, this.description, this.defaultValue,
new ItemDeprecation(reason, replacement));
}
private ItemMetadata getFirstItemWithName(ConfigurationMetadata metadata,