[bs-93] Add /info endpoint with git properties etc.
* If git.properties is on the classpath (e.g. from the Maven plugin) /info will list the commit id, branch and dates. * If application.yml has an info object at the top level that will be diplayed as well (so e.g. you can use Maven resource filtering top add the project name, version etc.) * RelaxedDataBinder can now be used to bind to a Map (as opposed to a nested Map inside teh target bean) [Fixes #49130073]
This commit is contained in:
parent
7e6651c0a2
commit
7e548b5bd4
|
|
@ -10,7 +10,6 @@
|
||||||
<artifactId>spring-bootstrap-applications</artifactId>
|
<artifactId>spring-bootstrap-applications</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<properties>
|
<properties>
|
||||||
<main.basedir>${project.basedir}/..</main.basedir>
|
|
||||||
<spring.bootstrap.version>0.0.1-SNAPSHOT</spring.bootstrap.version>
|
<spring.bootstrap.version>0.0.1-SNAPSHOT</spring.bootstrap.version>
|
||||||
<start-class>org.springframework.bootstrap.main.Spring</start-class>
|
<start-class>org.springframework.bootstrap.main.Spring</start-class>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2013 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.bootstrap.autoconfigure.service;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import javax.servlet.Servlet;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.bootstrap.bind.PropertiesConfigurationFactory;
|
||||||
|
import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
|
||||||
|
import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean;
|
||||||
|
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
|
||||||
|
import org.springframework.bootstrap.service.info.InfoEndpoint;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.env.ConfigurableEnvironment;
|
||||||
|
import org.springframework.core.env.StandardEnvironment;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.core.io.support.PropertiesLoaderUtils;
|
||||||
|
import org.springframework.web.servlet.DispatcherServlet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link EnableAutoConfiguration Auto-configuration} for /info endpoint.
|
||||||
|
*
|
||||||
|
* @author Dave Syer
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
|
||||||
|
@ConditionalOnMissingBean({ InfoEndpoint.class })
|
||||||
|
public class InfoConfiguration {
|
||||||
|
|
||||||
|
@Resource(name = "infoMap")
|
||||||
|
private Map<String, Object> infoMap;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("gitInfo")
|
||||||
|
private GitInfo gitInfo;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Map<String, Object> applicationInfo() {
|
||||||
|
LinkedHashMap<String, Object> info = new LinkedHashMap<String, Object>();
|
||||||
|
info.putAll(this.infoMap);
|
||||||
|
if (this.gitInfo.getBranch() != null) {
|
||||||
|
info.put("git", this.gitInfo);
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public InfoEndpoint infoEndpoint() {
|
||||||
|
return new InfoEndpoint(applicationInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public static class InfoPropertiesConfiguration {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ConfigurableEnvironment environment = new StandardEnvironment();
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PropertiesConfigurationFactory<GitInfo> gitInfo() throws IOException {
|
||||||
|
PropertiesConfigurationFactory<GitInfo> factory = new PropertiesConfigurationFactory<GitInfo>(
|
||||||
|
new GitInfo());
|
||||||
|
factory.setTargetName("git");
|
||||||
|
Properties properties = new Properties();
|
||||||
|
if (new ClassPathResource("git.properties").exists()) {
|
||||||
|
properties = PropertiesLoaderUtils.loadProperties(new ClassPathResource(
|
||||||
|
"git.properties"));
|
||||||
|
}
|
||||||
|
factory.setProperties(properties);
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PropertiesConfigurationFactory<Map<String, Object>> infoMap() {
|
||||||
|
PropertiesConfigurationFactory<Map<String, Object>> factory = new PropertiesConfigurationFactory<Map<String, Object>>(
|
||||||
|
new LinkedHashMap<String, Object>());
|
||||||
|
factory.setTargetName("info");
|
||||||
|
factory.setPropertySources(this.environment.getPropertySources());
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class GitInfo {
|
||||||
|
private String branch;
|
||||||
|
private Commit commit = new Commit();
|
||||||
|
|
||||||
|
public String getBranch() {
|
||||||
|
return this.branch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBranch(String branch) {
|
||||||
|
this.branch = branch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Commit getCommit() {
|
||||||
|
return this.commit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Commit {
|
||||||
|
private String id;
|
||||||
|
private String time;
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return this.id == null ? "" : (this.id.length() > 7 ? this.id.substring(
|
||||||
|
0, 7) : this.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTime() {
|
||||||
|
return this.time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTime(String time) {
|
||||||
|
this.time = time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -61,6 +61,9 @@ public class SecurityConfiguration {
|
||||||
@Value("${endpoints.healthz.path:/healthz}")
|
@Value("${endpoints.healthz.path:/healthz}")
|
||||||
private String healthzPath = "/healthz";
|
private String healthzPath = "/healthz";
|
||||||
|
|
||||||
|
@Value("${endpoints.info.path:/info}")
|
||||||
|
private String infoPath = "/info";
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SecurityProperties security;
|
private SecurityProperties security;
|
||||||
|
|
||||||
|
|
@ -70,6 +73,7 @@ public class SecurityConfiguration {
|
||||||
@Override
|
@Override
|
||||||
protected void ignoredRequests(IgnoredRequestRegistry ignoredRequests) {
|
protected void ignoredRequests(IgnoredRequestRegistry ignoredRequests) {
|
||||||
ignoredRequests.antMatchers(this.healthzPath);
|
ignoredRequests.antMatchers(this.healthzPath);
|
||||||
|
ignoredRequests.antMatchers(this.infoPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ import org.springframework.bootstrap.service.properties.ServerProperties;
|
||||||
import org.springframework.bootstrap.service.properties.ServerProperties.Tomcat;
|
import org.springframework.bootstrap.service.properties.ServerProperties.Tomcat;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
|
@ -50,6 +51,7 @@ import org.springframework.util.StringUtils;
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConditionalOnClass({ Servlet.class })
|
@ConditionalOnClass({ Servlet.class })
|
||||||
@Order(Integer.MIN_VALUE)
|
@Order(Integer.MIN_VALUE)
|
||||||
|
@Import(InfoConfiguration.class)
|
||||||
public class ServerConfiguration implements BeanPostProcessor, BeanFactoryAware {
|
public class ServerConfiguration implements BeanPostProcessor, BeanFactoryAware {
|
||||||
|
|
||||||
private BeanFactory beanFactory;
|
private BeanFactory beanFactory;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2013 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.bootstrap.service.info;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dave Syer
|
||||||
|
*/
|
||||||
|
@Controller
|
||||||
|
public class InfoEndpoint {
|
||||||
|
|
||||||
|
private Map<String, Object> info;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param info
|
||||||
|
*/
|
||||||
|
public InfoEndpoint(Map<String, Object> info) {
|
||||||
|
this.info = new LinkedHashMap<String, Object>(info);
|
||||||
|
this.info.putAll(getAdditionalInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping("${endpoints.info.path:/info}")
|
||||||
|
@ResponseBody
|
||||||
|
public Map<String, Object> info() {
|
||||||
|
return this.info;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Map<String, Object> getAdditionalInfo() {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -30,6 +30,9 @@ import org.springframework.bootstrap.context.annotation.ConfigurationProperties;
|
||||||
@ConfigurationProperties(name = "endpoints", ignoreUnknownFields = false)
|
@ConfigurationProperties(name = "endpoints", ignoreUnknownFields = false)
|
||||||
public class EndpointsProperties {
|
public class EndpointsProperties {
|
||||||
|
|
||||||
|
@Valid
|
||||||
|
private Endpoint info = new Endpoint("/info");
|
||||||
|
|
||||||
@Valid
|
@Valid
|
||||||
private Endpoint varz = new Endpoint("/varz");
|
private Endpoint varz = new Endpoint("/varz");
|
||||||
|
|
||||||
|
|
@ -48,6 +51,10 @@ public class EndpointsProperties {
|
||||||
@Valid
|
@Valid
|
||||||
private Endpoint dump = new Endpoint("/dump");
|
private Endpoint dump = new Endpoint("/dump");
|
||||||
|
|
||||||
|
public Endpoint getInfo() {
|
||||||
|
return this.info;
|
||||||
|
}
|
||||||
|
|
||||||
public Endpoint getVarz() {
|
public Endpoint getVarz() {
|
||||||
return this.varz;
|
return this.varz;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,16 @@ public class RelaxedDataBinder extends DataBinder {
|
||||||
* @param target the target into which properties are bound
|
* @param target the target into which properties are bound
|
||||||
*/
|
*/
|
||||||
public RelaxedDataBinder(Object target) {
|
public RelaxedDataBinder(Object target) {
|
||||||
super(target);
|
super(wrapTarget(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object wrapTarget(Object target) {
|
||||||
|
if (target instanceof Map) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> map = (Map<String, Object>) target;
|
||||||
|
target = new MapHolder(map);
|
||||||
|
}
|
||||||
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -51,7 +60,7 @@ public class RelaxedDataBinder extends DataBinder {
|
||||||
* @param namePrefix An optional prefix to be used when reading properties
|
* @param namePrefix An optional prefix to be used when reading properties
|
||||||
*/
|
*/
|
||||||
public RelaxedDataBinder(Object target, String namePrefix) {
|
public RelaxedDataBinder(Object target, String namePrefix) {
|
||||||
super(target, (StringUtils.hasLength(namePrefix) ? namePrefix
|
super(wrapTarget(target), (StringUtils.hasLength(namePrefix) ? namePrefix
|
||||||
: DEFAULT_OBJECT_NAME));
|
: DEFAULT_OBJECT_NAME));
|
||||||
this.namePrefix = (StringUtils.hasLength(namePrefix) ? namePrefix + "." : null);
|
this.namePrefix = (StringUtils.hasLength(namePrefix) ? namePrefix + "." : null);
|
||||||
}
|
}
|
||||||
|
|
@ -74,11 +83,15 @@ public class RelaxedDataBinder extends DataBinder {
|
||||||
private MutablePropertyValues modifyProperties(MutablePropertyValues propertyValues,
|
private MutablePropertyValues modifyProperties(MutablePropertyValues propertyValues,
|
||||||
Object target) {
|
Object target) {
|
||||||
|
|
||||||
|
propertyValues = getProperyValuesForNamePrefix(propertyValues);
|
||||||
|
|
||||||
|
if (target instanceof MapHolder) {
|
||||||
|
propertyValues = addMapPrefix(propertyValues);
|
||||||
|
}
|
||||||
|
|
||||||
BeanWrapper targetWrapper = new BeanWrapperImpl(target);
|
BeanWrapper targetWrapper = new BeanWrapperImpl(target);
|
||||||
targetWrapper.setAutoGrowNestedPaths(true);
|
targetWrapper.setAutoGrowNestedPaths(true);
|
||||||
|
|
||||||
propertyValues = getProperyValuesForNamePrefix(propertyValues);
|
|
||||||
|
|
||||||
List<PropertyValue> list = propertyValues.getPropertyValueList();
|
List<PropertyValue> list = propertyValues.getPropertyValueList();
|
||||||
for (int i = 0; i < list.size(); i++) {
|
for (int i = 0; i < list.size(); i++) {
|
||||||
modifyProperty(propertyValues, targetWrapper, list.get(i), i);
|
modifyProperty(propertyValues, targetWrapper, list.get(i), i);
|
||||||
|
|
@ -86,6 +99,14 @@ public class RelaxedDataBinder extends DataBinder {
|
||||||
return propertyValues;
|
return propertyValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MutablePropertyValues addMapPrefix(MutablePropertyValues propertyValues) {
|
||||||
|
MutablePropertyValues rtn = new MutablePropertyValues();
|
||||||
|
for (PropertyValue pv : propertyValues.getPropertyValues()) {
|
||||||
|
rtn.add("map." + pv.getName(), pv.getValue());
|
||||||
|
}
|
||||||
|
return rtn;
|
||||||
|
}
|
||||||
|
|
||||||
private MutablePropertyValues getProperyValuesForNamePrefix(
|
private MutablePropertyValues getProperyValuesForNamePrefix(
|
||||||
MutablePropertyValues propertyValues) {
|
MutablePropertyValues propertyValues) {
|
||||||
if (this.namePrefix == null) {
|
if (this.namePrefix == null) {
|
||||||
|
|
@ -126,7 +147,6 @@ public class RelaxedDataBinder extends DataBinder {
|
||||||
// Any nested properties that are maps, are assumed to be simple nested
|
// Any nested properties that are maps, are assumed to be simple nested
|
||||||
// maps of maps...
|
// maps of maps...
|
||||||
if (type != null && Map.class.isAssignableFrom(type)) {
|
if (type != null && Map.class.isAssignableFrom(type)) {
|
||||||
String suffix = name.substring(base.length());
|
|
||||||
Map<String, Object> nested = new LinkedHashMap<String, Object>();
|
Map<String, Object> nested = new LinkedHashMap<String, Object>();
|
||||||
if (target.getPropertyValue(base) != null) {
|
if (target.getPropertyValue(base) != null) {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
|
@ -136,24 +156,33 @@ public class RelaxedDataBinder extends DataBinder {
|
||||||
} else {
|
} else {
|
||||||
target.setPropertyValue(base, nested);
|
target.setPropertyValue(base, nested);
|
||||||
}
|
}
|
||||||
Map<String, Object> value = nested;
|
modifyPopertiesForMap(nested, propertyValues, index, base);
|
||||||
nested = new LinkedHashMap<String, Object>();
|
|
||||||
String[] tree = StringUtils.delimitedListToStringArray(suffix, ".");
|
|
||||||
for (int j = 1; j < tree.length - 1; j++) {
|
|
||||||
if (!value.containsKey(tree[j])) {
|
|
||||||
value.put(tree[j], nested);
|
|
||||||
}
|
|
||||||
value = nested;
|
|
||||||
nested = new LinkedHashMap<String, Object>();
|
|
||||||
}
|
|
||||||
String refName = base + suffix.replaceAll("\\.([a-zA-Z0-9]*)", "[$1]");
|
|
||||||
propertyValues.setPropertyValueAt(new PropertyValue(refName,
|
|
||||||
propertyValue.getValue()), index);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void modifyPopertiesForMap(Map<String, Object> target,
|
||||||
|
MutablePropertyValues propertyValues, int index, String base) {
|
||||||
|
PropertyValue propertyValue = propertyValues.getPropertyValueList().get(index);
|
||||||
|
String name = propertyValue.getName();
|
||||||
|
String suffix = name.substring(base.length());
|
||||||
|
Map<String, Object> value = new LinkedHashMap<String, Object>();
|
||||||
|
String[] tree = StringUtils.delimitedListToStringArray(
|
||||||
|
suffix.startsWith(".") ? suffix.substring(1) : suffix, ".");
|
||||||
|
for (int j = 0; j < tree.length - 1; j++) {
|
||||||
|
if (!target.containsKey(tree[j])) {
|
||||||
|
target.put(tree[j], value);
|
||||||
|
}
|
||||||
|
target = value;
|
||||||
|
value = new LinkedHashMap<String, Object>();
|
||||||
|
}
|
||||||
|
String refName = base + suffix.replaceAll("\\.([a-zA-Z0-9]*)", "[$1]");
|
||||||
|
propertyValues.setPropertyValueAt(
|
||||||
|
new PropertyValue(refName, propertyValue.getValue()), index);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private String getActualPropertyName(BeanWrapper target, String prefix, String name) {
|
private String getActualPropertyName(BeanWrapper target, String prefix, String name) {
|
||||||
for (Variation variation : Variation.values()) {
|
for (Variation variation : Variation.values()) {
|
||||||
for (Manipulation manipulation : Manipulation.values()) {
|
for (Manipulation manipulation : Manipulation.values()) {
|
||||||
|
|
@ -223,4 +252,20 @@ public class RelaxedDataBinder extends DataBinder {
|
||||||
public abstract String apply(String value);
|
public abstract String apply(String value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class MapHolder {
|
||||||
|
private Map<String, Object> map;
|
||||||
|
|
||||||
|
public MapHolder(Map<String, Object> map) {
|
||||||
|
this.map = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMap(Map<String, Object> map) {
|
||||||
|
this.map = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getMap() {
|
||||||
|
return this.map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
@ -181,6 +182,26 @@ public class RelaxedDataBinderTests {
|
||||||
assertEquals(123, target.getValue());
|
assertEquals(123, target.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBindMap() throws Exception {
|
||||||
|
Map<String, Object> target = new LinkedHashMap<String, Object>();
|
||||||
|
BindingResult result = bind(target, "spam: bar\n" + "vanilla.value: 123",
|
||||||
|
"vanilla");
|
||||||
|
assertEquals(0, result.getErrorCount());
|
||||||
|
assertEquals("123", target.get("value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBindMapNestedInMap() throws Exception {
|
||||||
|
Map<String, Object> target = new LinkedHashMap<String, Object>();
|
||||||
|
BindingResult result = bind(target, "spam: bar\n" + "vanilla.foo.value: 123",
|
||||||
|
"vanilla");
|
||||||
|
assertEquals(0, result.getErrorCount());
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> map = (Map<String, Object>) target.get("foo");
|
||||||
|
assertEquals("123", map.get("value"));
|
||||||
|
}
|
||||||
|
|
||||||
private BindingResult bind(Object target, String values) throws Exception {
|
private BindingResult bind(Object target, String values) throws Exception {
|
||||||
return bind(target, values, null);
|
return bind(target, values, null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue