Merge branch '1.5.x'

pull/10948/merge
Andy Wilkinson 7 years ago
commit cf485ce144

@ -24,10 +24,12 @@ import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
@ -38,6 +40,8 @@ import com.fasterxml.jackson.databind.ser.PropertyWriter;
import com.fasterxml.jackson.databind.ser.SerializerFactory;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.boot.actuate.endpoint.Sanitizer;
@ -68,7 +72,7 @@ import org.springframework.util.StringUtils;
@Endpoint(id = "configprops")
public class ConfigurationPropertiesReportEndpoint implements ApplicationContextAware {
private static final String CGLIB_FILTER_ID = "cglibFilter";
private static final String CONFIGURATION_PROPERTIES_FILTER_ID = "configurationPropertiesFilter";
private final Sanitizer sanitizer = new Sanitizer();
@ -167,7 +171,7 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
protected void configureObjectMapper(ObjectMapper mapper) {
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
mapper.setSerializationInclusion(Include.NON_NULL);
applyCglibFilters(mapper);
applyConfigurationPropertiesFilter(mapper);
applySerializationModifier(mapper);
}
@ -181,15 +185,11 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
mapper.setSerializerFactory(factory);
}
/**
* Configure PropertyFilter to make sure Jackson doesn't process CGLIB generated bean
* properties.
* @param mapper the object mapper
*/
private void applyCglibFilters(ObjectMapper mapper) {
mapper.setAnnotationIntrospector(new CglibAnnotationIntrospector());
mapper.setFilterProvider(new SimpleFilterProvider().addFilter(CGLIB_FILTER_ID,
new CglibBeanPropertyFilter()));
private void applyConfigurationPropertiesFilter(ObjectMapper mapper) {
mapper.setAnnotationIntrospector(
new ConfigurationPropertiesAnnotationIntrospector());
mapper.setFilterProvider(new SimpleFilterProvider()
.setDefaultFilter(new ConfigurationPropertiesPropertyFilter()));
}
/**
@ -268,14 +268,14 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
* properties.
*/
@SuppressWarnings("serial")
private static class CglibAnnotationIntrospector
private static class ConfigurationPropertiesAnnotationIntrospector
extends JacksonAnnotationIntrospector {
@Override
public Object findFilterId(Annotated a) {
Object id = super.findFilterId(a);
if (id == null) {
id = CGLIB_FILTER_ID;
id = CONFIGURATION_PROPERTIES_FILTER_ID;
}
return id;
}
@ -283,10 +283,20 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
}
/**
* {@link SimpleBeanPropertyFilter} to filter out all bean properties whose names
* start with '$$'.
* {@link SimpleBeanPropertyFilter} for serialization of
* {@link ConfigurationProperties} beans. The filter hides:
*
* <ul>
* <li>Properties that have a name starting with '$$'.
* <li>Properties that are self-referential.
* <li>Properties that throw an exception when retrieving their value.
* </ul>
*/
private static class CglibBeanPropertyFilter extends SimpleBeanPropertyFilter {
private static class ConfigurationPropertiesPropertyFilter
extends SimpleBeanPropertyFilter {
private static final Log logger = LogFactory
.getLog(ConfigurationPropertiesPropertyFilter.class);
@Override
protected boolean include(BeanPropertyWriter writer) {
@ -302,6 +312,31 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
return !name.startsWith("$$");
}
@Override
public void serializeAsField(Object pojo, JsonGenerator jgen,
SerializerProvider provider, PropertyWriter writer) throws Exception {
if (writer instanceof BeanPropertyWriter) {
try {
if (pojo == ((BeanPropertyWriter) writer).get(pojo)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping '" + writer.getFullName() + "' on '"
+ pojo.getClass().getName()
+ "' as it is self-referential");
}
return;
}
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping '" + writer.getFullName() + "' on '"
+ pojo.getClass().getName() + "' as an exception "
+ "was thrown when retrieving its value", ex);
}
return;
}
}
super.serializeAsField(pojo, jgen, provider, writer);
}
}
/**

@ -22,6 +22,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.zaxxer.hikari.HikariDataSource;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor;
@ -87,9 +89,10 @@ public class ConfigurationPropertiesReportEndpointSerializationTests {
}
@Test
public void testCycle() throws Exception {
@SuppressWarnings("unchecked")
public void testSelfReferentialProperty() throws Exception {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withUserConfiguration(CycleConfig.class)
.withUserConfiguration(SelfReferentialConfig.class)
.withPropertyValues("foo.name:foo");
contextRunner.run((context) -> {
ConfigurationPropertiesReportEndpoint endpoint = context
@ -97,12 +100,34 @@ public class ConfigurationPropertiesReportEndpointSerializationTests {
ConfigurationPropertiesDescriptor properties = endpoint
.configurationProperties();
ConfigurationPropertiesBeanDescriptor foo = properties.getBeans().get("foo");
assertThat(foo).isNotNull();
assertThat(foo.getPrefix()).isEqualTo("foo");
Map<String, Object> map = foo.getProperties();
assertThat(map).isNotNull();
assertThat(map).hasSize(1);
assertThat(map.get("error")).isEqualTo("Cannot serialize 'foo'");
assertThat(map).containsOnlyKeys("bar", "name");
assertThat(map).containsEntry("name", "foo");
Map<String, Object> bar = (Map<String, Object>) map.get("bar");
assertThat(bar).containsOnlyKeys("name");
assertThat(bar).containsEntry("name", "123456");
});
}
@Test
@Ignore("gh-11037")
public void testCycle() {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withUserConfiguration(CycleConfig.class);
contextRunner.run((context) -> {
ConfigurationPropertiesReportEndpoint endpoint = context
.getBean(ConfigurationPropertiesReportEndpoint.class);
ConfigurationPropertiesDescriptor properties = endpoint
.configurationProperties();
ConfigurationPropertiesBeanDescriptor cycle = properties.getBeans()
.get("cycle");
assertThat(cycle.getPrefix()).isEqualTo("cycle");
Map<String, Object> map = cycle.getProperties();
assertThat(map).isNotNull();
assertThat(map).containsOnlyKeys("error");
assertThat(map).containsEntry("error", "Cannot serialize 'cycle'");
});
}
@ -191,7 +216,6 @@ public class ConfigurationPropertiesReportEndpointSerializationTests {
@Test
@SuppressWarnings("unchecked")
public void testInitializedMapAndList() throws Exception {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withUserConfiguration(InitializedMapAndListPropertiesConfig.class)
@ -213,6 +237,22 @@ public class ConfigurationPropertiesReportEndpointSerializationTests {
});
}
@Test
public void hikariDataSourceConfigurationPropertiesBeanCanBeSerialized() {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withUserConfiguration(HikariDataSourceConfig.class);
contextRunner.run((context) -> {
ConfigurationPropertiesReportEndpoint endpoint = context
.getBean(ConfigurationPropertiesReportEndpoint.class);
ConfigurationPropertiesDescriptor properties = endpoint
.configurationProperties();
ConfigurationPropertiesBeanDescriptor hikariDataSource = properties.getBeans()
.get("hikariDataSource");
Map<String, Object> nestedProperties = hikariDataSource.getProperties();
assertThat(nestedProperties).doesNotContainKey("error");
});
}
@Configuration
@EnableConfigurationProperties
public static class Base {
@ -238,12 +278,12 @@ public class ConfigurationPropertiesReportEndpointSerializationTests {
@Configuration
@Import(Base.class)
public static class CycleConfig {
public static class SelfReferentialConfig {
@Bean
@ConfigurationProperties(prefix = "foo")
public Cycle foo() {
return new Cycle();
public SelfReferential foo() {
return new SelfReferential();
}
}
@ -254,8 +294,8 @@ public class ConfigurationPropertiesReportEndpointSerializationTests {
@Bean
@ConfigurationProperties(prefix = "bar")
public Cycle foo() {
return new Cycle();
public SelfReferential foo() {
return new SelfReferential();
}
}
@ -363,11 +403,11 @@ public class ConfigurationPropertiesReportEndpointSerializationTests {
}
public static class Cycle extends Foo {
public static class SelfReferential extends Foo {
private Foo self;
public Cycle() {
public SelfReferential() {
this.self = this;
}
@ -439,4 +479,58 @@ public class ConfigurationPropertiesReportEndpointSerializationTests {
}
static class Cycle {
private final Alpha alpha = new Alpha(this);
public Alpha getAlpha() {
return this.alpha;
}
static class Alpha {
private final Cycle cycle;
Alpha(Cycle cycle) {
this.cycle = cycle;
}
public Cycle getCycle() {
return this.cycle;
}
}
}
@Configuration
@Import(Base.class)
static class CycleConfig {
@Bean
// gh-11037
// @ConfigurationProperties(prefix = "cycle")
public Cycle cycle() {
return new Cycle();
}
}
@Configuration
@EnableConfigurationProperties
static class HikariDataSourceConfig {
@Bean
public ConfigurationPropertiesReportEndpoint endpoint() {
return new ConfigurationPropertiesReportEndpoint();
}
@Bean
@ConfigurationProperties(prefix = "test.datasource")
public HikariDataSource hikariDataSource() {
return new HikariDataSource();
}
}
}

Loading…
Cancel
Save