Merge branch '1.5.x'
This commit is contained in:
commit
cf485ce144
|
|
@ -24,10 +24,12 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
import com.fasterxml.jackson.databind.BeanDescription;
|
import com.fasterxml.jackson.databind.BeanDescription;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.SerializationConfig;
|
import com.fasterxml.jackson.databind.SerializationConfig;
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
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.Annotated;
|
||||||
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
|
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
|
||||||
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
|
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.SerializerFactory;
|
||||||
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
|
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
|
||||||
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
|
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.beans.BeansException;
|
||||||
import org.springframework.boot.actuate.endpoint.Sanitizer;
|
import org.springframework.boot.actuate.endpoint.Sanitizer;
|
||||||
|
|
@ -68,7 +72,7 @@ import org.springframework.util.StringUtils;
|
||||||
@Endpoint(id = "configprops")
|
@Endpoint(id = "configprops")
|
||||||
public class ConfigurationPropertiesReportEndpoint implements ApplicationContextAware {
|
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();
|
private final Sanitizer sanitizer = new Sanitizer();
|
||||||
|
|
||||||
|
|
@ -167,7 +171,7 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
||||||
protected void configureObjectMapper(ObjectMapper mapper) {
|
protected void configureObjectMapper(ObjectMapper mapper) {
|
||||||
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
|
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
|
||||||
mapper.setSerializationInclusion(Include.NON_NULL);
|
mapper.setSerializationInclusion(Include.NON_NULL);
|
||||||
applyCglibFilters(mapper);
|
applyConfigurationPropertiesFilter(mapper);
|
||||||
applySerializationModifier(mapper);
|
applySerializationModifier(mapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,15 +185,11 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
||||||
mapper.setSerializerFactory(factory);
|
mapper.setSerializerFactory(factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void applyConfigurationPropertiesFilter(ObjectMapper mapper) {
|
||||||
* Configure PropertyFilter to make sure Jackson doesn't process CGLIB generated bean
|
mapper.setAnnotationIntrospector(
|
||||||
* properties.
|
new ConfigurationPropertiesAnnotationIntrospector());
|
||||||
* @param mapper the object mapper
|
mapper.setFilterProvider(new SimpleFilterProvider()
|
||||||
*/
|
.setDefaultFilter(new ConfigurationPropertiesPropertyFilter()));
|
||||||
private void applyCglibFilters(ObjectMapper mapper) {
|
|
||||||
mapper.setAnnotationIntrospector(new CglibAnnotationIntrospector());
|
|
||||||
mapper.setFilterProvider(new SimpleFilterProvider().addFilter(CGLIB_FILTER_ID,
|
|
||||||
new CglibBeanPropertyFilter()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -268,14 +268,14 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
||||||
* properties.
|
* properties.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
private static class CglibAnnotationIntrospector
|
private static class ConfigurationPropertiesAnnotationIntrospector
|
||||||
extends JacksonAnnotationIntrospector {
|
extends JacksonAnnotationIntrospector {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object findFilterId(Annotated a) {
|
public Object findFilterId(Annotated a) {
|
||||||
Object id = super.findFilterId(a);
|
Object id = super.findFilterId(a);
|
||||||
if (id == null) {
|
if (id == null) {
|
||||||
id = CGLIB_FILTER_ID;
|
id = CONFIGURATION_PROPERTIES_FILTER_ID;
|
||||||
}
|
}
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
@ -283,10 +283,20 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link SimpleBeanPropertyFilter} to filter out all bean properties whose names
|
* {@link SimpleBeanPropertyFilter} for serialization of
|
||||||
* start with '$$'.
|
* {@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
|
@Override
|
||||||
protected boolean include(BeanPropertyWriter writer) {
|
protected boolean include(BeanPropertyWriter writer) {
|
||||||
|
|
@ -302,6 +312,31 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
||||||
return !name.startsWith("$$");
|
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.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor;
|
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor;
|
||||||
|
|
@ -87,9 +89,10 @@ public class ConfigurationPropertiesReportEndpointSerializationTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCycle() throws Exception {
|
@SuppressWarnings("unchecked")
|
||||||
|
public void testSelfReferentialProperty() throws Exception {
|
||||||
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||||
.withUserConfiguration(CycleConfig.class)
|
.withUserConfiguration(SelfReferentialConfig.class)
|
||||||
.withPropertyValues("foo.name:foo");
|
.withPropertyValues("foo.name:foo");
|
||||||
contextRunner.run((context) -> {
|
contextRunner.run((context) -> {
|
||||||
ConfigurationPropertiesReportEndpoint endpoint = context
|
ConfigurationPropertiesReportEndpoint endpoint = context
|
||||||
|
|
@ -97,12 +100,34 @@ public class ConfigurationPropertiesReportEndpointSerializationTests {
|
||||||
ConfigurationPropertiesDescriptor properties = endpoint
|
ConfigurationPropertiesDescriptor properties = endpoint
|
||||||
.configurationProperties();
|
.configurationProperties();
|
||||||
ConfigurationPropertiesBeanDescriptor foo = properties.getBeans().get("foo");
|
ConfigurationPropertiesBeanDescriptor foo = properties.getBeans().get("foo");
|
||||||
assertThat(foo).isNotNull();
|
|
||||||
assertThat(foo.getPrefix()).isEqualTo("foo");
|
assertThat(foo.getPrefix()).isEqualTo("foo");
|
||||||
Map<String, Object> map = foo.getProperties();
|
Map<String, Object> map = foo.getProperties();
|
||||||
assertThat(map).isNotNull();
|
assertThat(map).isNotNull();
|
||||||
assertThat(map).hasSize(1);
|
assertThat(map).containsOnlyKeys("bar", "name");
|
||||||
assertThat(map.get("error")).isEqualTo("Cannot serialize 'foo'");
|
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
|
@Test
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
|
||||||
public void testInitializedMapAndList() throws Exception {
|
public void testInitializedMapAndList() throws Exception {
|
||||||
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||||
.withUserConfiguration(InitializedMapAndListPropertiesConfig.class)
|
.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
|
@Configuration
|
||||||
@EnableConfigurationProperties
|
@EnableConfigurationProperties
|
||||||
public static class Base {
|
public static class Base {
|
||||||
|
|
@ -238,12 +278,12 @@ public class ConfigurationPropertiesReportEndpointSerializationTests {
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@Import(Base.class)
|
@Import(Base.class)
|
||||||
public static class CycleConfig {
|
public static class SelfReferentialConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConfigurationProperties(prefix = "foo")
|
@ConfigurationProperties(prefix = "foo")
|
||||||
public Cycle foo() {
|
public SelfReferential foo() {
|
||||||
return new Cycle();
|
return new SelfReferential();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -254,8 +294,8 @@ public class ConfigurationPropertiesReportEndpointSerializationTests {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConfigurationProperties(prefix = "bar")
|
@ConfigurationProperties(prefix = "bar")
|
||||||
public Cycle foo() {
|
public SelfReferential foo() {
|
||||||
return new Cycle();
|
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;
|
private Foo self;
|
||||||
|
|
||||||
public Cycle() {
|
public SelfReferential() {
|
||||||
this.self = this;
|
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…
Reference in New Issue