Browse Source

DATACMNS-630 - Added HandlerMethodArgumentResolver to create proxies for interfaces.

We now ship a ProxyingHandlerMethodArgumentResolver that gets registered when @EnableSpringDataWebSupport is activated. It creates Map-based proxy instances for interfaces used as Spring MVC controller method parameters.
pull/113/head
Oliver Gierke 11 years ago
parent
commit
57a42ababd
  1. 301
      src/main/java/org/springframework/data/web/MapDataBinder.java
  2. 93
      src/main/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolver.java
  3. 12
      src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java
  4. 2
      src/test/java/org/springframework/data/projection/MapAccessingMethodInterceptorUnitTests.java
  5. 142
      src/test/java/org/springframework/data/web/MapDataBinderUnitTests.java
  6. 31
      src/test/java/org/springframework/data/web/config/EnableSpringDataWebSupportIntegrationTests.java
  7. 74
      src/test/java/org/springframework/data/web/config/SampleController.java

301
src/main/java/org/springframework/data/web/MapDataBinder.java

@ -0,0 +1,301 @@ @@ -0,0 +1,301 @@
/*
* Copyright 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.data.web;
import java.beans.PropertyDescriptor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.AbstractPropertyAccessor;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.ConfigurablePropertyAccessor;
import org.springframework.beans.NotWritablePropertyException;
import org.springframework.beans.PropertyAccessor;
import org.springframework.context.expression.MapAccessor;
import org.springframework.core.CollectionFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.mapping.PropertyReferenceException;
import org.springframework.data.util.TypeInformation;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.expression.spel.support.StandardTypeConverter;
import org.springframework.util.Assert;
import org.springframework.web.bind.WebDataBinder;
/**
* A {@link WebDataBinder} that automatically binds all properties exposed in the given type using a {@link Map}.
*
* @author Oliver Gierke
* @since 1.10
*/
class MapDataBinder extends WebDataBinder {
private final Class<?> type;
private final ConversionService conversionService;
/**
* Creates a new {@link MapDataBinder} for the given type and {@link ConversionService}.
*
* @param type target type to detect property that need to be bound.
* @param conversionService the {@link ConversionService} to be used to preprocess values.
*/
public MapDataBinder(Class<?> type, ConversionService conversionService) {
super(new HashMap<String, Object>());
this.type = type;
this.conversionService = conversionService;
}
/*
* (non-Javadoc)
* @see org.springframework.validation.DataBinder#getTarget()
*/
@Override
@SuppressWarnings("unchecked")
public Map<String, Object> getTarget() {
return (Map<String, Object>) super.getTarget();
}
/*
* (non-Javadoc)
* @see org.springframework.validation.DataBinder#getPropertyAccessor()
*/
@Override
protected ConfigurablePropertyAccessor getPropertyAccessor() {
return new MapPropertyAccessor(type, getTarget(), conversionService);
}
/**
* {@link PropertyAccessor} to store and retrieve values in a {@link Map}. Uses Spring Expression language to create
* deeply nested Map structures.
*
* @author Oliver Gierke
* @since 1.10
*/
private static class MapPropertyAccessor extends AbstractPropertyAccessor {
private static final SpelExpressionParser PARSER = new SpelExpressionParser(
new SpelParserConfiguration(false, true));
private final Class<?> type;
private final Map<String, Object> map;
private final ConversionService conversionService;
/**
* Creates a new {@link MapPropertyAccessor} for the given type, map and {@link ConversionService}.
*
* @param type must not be {@literal null}.
* @param map must not be {@literal null}.
* @param conversionService must not be {@literal null}.
*/
public MapPropertyAccessor(Class<?> type, Map<String, Object> map, ConversionService conversionService) {
Assert.notNull(type, "Type must not be null!");
Assert.notNull(map, "Map must not be null!");
Assert.notNull(conversionService, "ConversionService must not be null!");
this.type = type;
this.map = map;
this.conversionService = conversionService;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.PropertyAccessor#isReadableProperty(java.lang.String)
*/
@Override
public boolean isReadableProperty(String propertyName) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
* @see org.springframework.beans.PropertyAccessor#isWritableProperty(java.lang.String)
*/
@Override
public boolean isWritableProperty(String propertyName) {
try {
return getPropertyPath(propertyName) != null;
} catch (PropertyReferenceException o_O) {
return false;
}
}
/*
* (non-Javadoc)
* @see org.springframework.beans.PropertyAccessor#getPropertyTypeDescriptor(java.lang.String)
*/
@Override
public TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
* @see org.springframework.beans.AbstractPropertyAccessor#getPropertyValue(java.lang.String)
*/
@Override
public Object getPropertyValue(String propertyName) throws BeansException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
* @see org.springframework.beans.AbstractPropertyAccessor#setPropertyValue(java.lang.String, java.lang.Object)
*/
@Override
public void setPropertyValue(String propertyName, Object value) throws BeansException {
if (!isWritableProperty(propertyName)) {
throw new NotWritablePropertyException(type, propertyName);
}
StandardEvaluationContext context = new StandardEvaluationContext();
context.addPropertyAccessor(new PropertyTraversingMapAccessor(type, new DefaultConversionService()));
context.setTypeConverter(new StandardTypeConverter(conversionService));
context.setRootObject(map);
Expression expression = PARSER.parseExpression(propertyName);
PropertyPath leafProperty = getPropertyPath(propertyName).getLeafProperty();
TypeInformation<?> owningType = leafProperty.getOwningType();
TypeInformation<?> propertyType = owningType.getProperty(leafProperty.getSegment());
propertyType = propertyName.endsWith("]") ? propertyType.getActualType() : propertyType;
if (conversionRequired(value, propertyType.getType())) {
PropertyDescriptor descriptor = BeanUtils
.getPropertyDescriptor(owningType.getType(), leafProperty.getSegment());
MethodParameter methodParameter = new MethodParameter(descriptor.getReadMethod(), -1);
TypeDescriptor typeDescriptor = TypeDescriptor.nested(methodParameter, 0);
value = conversionService.convert(value, TypeDescriptor.forObject(value), typeDescriptor);
}
expression.setValue(context, value);
}
private boolean conversionRequired(Object source, Class<?> targetType) {
if (targetType.isInstance(source)) {
return false;
}
return conversionService.canConvert(source.getClass(), targetType);
}
private PropertyPath getPropertyPath(String propertyName) {
String plainPropertyPath = propertyName.replaceAll("\\[.*?\\]", "");
return PropertyPath.from(plainPropertyPath, type);
}
/**
* A special {@link MapAccessor} that traverses properties on the configured type to automatically create nested Map
* and collection values as necessary.
*
* @author Oliver Gierke
* @since 1.10
*/
private static final class PropertyTraversingMapAccessor extends MapAccessor {
private final ConversionService conversionService;
private Class<?> type;
/**
* Creates a new {@link PropertyTraversingMapAccessor} for the given type and {@link ConversionService}.
*
* @param type must not be {@literal null}.
* @param conversionService must not be {@literal null}.
*/
public PropertyTraversingMapAccessor(Class<?> type, ConversionService conversionService) {
Assert.notNull(type, "Type must not be null!");
Assert.notNull(conversionService, "ConversionService must not be null!");
this.type = type;
this.conversionService = conversionService;
}
/*
* (non-Javadoc)
* @see org.springframework.context.expression.MapAccessor#canRead(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.String)
*/
@Override
public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
return true;
}
/*
* (non-Javadoc)
* @see org.springframework.context.expression.MapAccessor#read(org.springframework.expression.EvaluationContext, java.lang.Object, java.lang.String)
*/
@Override
@SuppressWarnings("unchecked")
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
PropertyPath path = PropertyPath.from(name, type);
try {
return super.read(context, target, name);
} catch (AccessException o_O) {
Object emptyResult = path.isCollection() ? CollectionFactory.createCollection(List.class, 0)
: CollectionFactory.createMap(Map.class, 0);
((Map<String, Object>) target).put(name, emptyResult);
return new TypedValue(emptyResult, getDescriptor(path, emptyResult));
} finally {
this.type = path.getType();
}
}
/**
* Returns the type descriptor for the given {@link PropertyPath} and empty value for that path.
*
* @param path must not be {@literal null}.
* @param emptyValue must not be {@literal null}.
* @return
*/
private TypeDescriptor getDescriptor(PropertyPath path, Object emptyValue) {
Class<?> actualPropertyType = path.getType();
TypeDescriptor valueDescriptor = conversionService.canConvert(String.class, actualPropertyType) ? TypeDescriptor
.valueOf(String.class) : TypeDescriptor.valueOf(HashMap.class);
return path.isCollection() ? TypeDescriptor.collection(emptyValue.getClass(), valueDescriptor) : TypeDescriptor
.map(emptyValue.getClass(), TypeDescriptor.valueOf(String.class), valueDescriptor);
}
}
}
}

93
src/main/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolver.java

@ -0,0 +1,93 @@ @@ -0,0 +1,93 @@
/*
* Copyright 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.data.web;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.annotation.ModelAttributeMethodProcessor;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
/**
* {@link HandlerMethodArgumentResolver} to create Proxy instances for interface based controller method parameters.
*
* @author Oliver Gierke
* @since 1.10
*/
public class ProxyingHandlerMethodArgumentResolver extends ModelAttributeMethodProcessor implements BeanFactoryAware {
private final SpelAwareProxyProjectionFactory proxyFactory;
private final ConversionService conversionService;
/**
* Creates a new {@link PageableHandlerMethodArgumentResolver} using the given {@link ConversionService}.
*
* @param conversionService must not be {@literal null}.
*/
public ProxyingHandlerMethodArgumentResolver(ConversionService conversionService) {
super(true);
this.proxyFactory = new SpelAwareProxyProjectionFactory();
this.conversionService = conversionService;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
*/
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.proxyFactory.setBeanFactory(beanFactory);
}
/*
* (non-Javadoc)
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter)
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().isInterface();
}
/*
* (non-Javadoc)
* @see org.springframework.web.method.annotation.ModelAttributeMethodProcessor#createAttribute(java.lang.String, org.springframework.core.MethodParameter, org.springframework.web.bind.support.WebDataBinderFactory, org.springframework.web.context.request.NativeWebRequest)
*/
@Override
protected Object createAttribute(String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory,
NativeWebRequest request) throws Exception {
MapDataBinder binder = new MapDataBinder(parameter.getParameterType(), conversionService);
binder.bind(new MutablePropertyValues(request.getParameterMap()));
return proxyFactory.createProjection(parameter.getParameterType(), binder.getTarget());
}
/*
* (non-Javadoc)
* @see org.springframework.web.method.annotation.ModelAttributeMethodProcessor#bindRequestParameters(org.springframework.web.bind.WebDataBinder, org.springframework.web.context.request.NativeWebRequest)
*/
@Override
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {}
}

12
src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java

@ -17,14 +17,18 @@ package org.springframework.data.web.config; @@ -17,14 +17,18 @@ package org.springframework.data.web.config;
import java.util.List;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.geo.format.DistanceFormatter;
import org.springframework.data.geo.format.PointFormatter;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.data.web.ProxyingHandlerMethodArgumentResolver;
import org.springframework.data.web.SortHandlerMethodArgumentResolver;
import org.springframework.format.FormatterRegistry;
import org.springframework.format.support.FormattingConversionService;
@ -42,6 +46,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter @@ -42,6 +46,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
public class SpringDataWebConfiguration extends WebMvcConfigurerAdapter {
@Autowired private ApplicationContext context;
@Autowired @Qualifier("mvcConversionService") ObjectFactory<ConversionService> conversionService;
/*
* (non-Javadoc)
@ -75,10 +80,7 @@ public class SpringDataWebConfiguration extends WebMvcConfigurerAdapter { @@ -75,10 +80,7 @@ public class SpringDataWebConfiguration extends WebMvcConfigurerAdapter {
return;
}
registerDomainClassConverterFor((FormattingConversionService) registry);
}
private void registerDomainClassConverterFor(FormattingConversionService conversionService) {
FormattingConversionService conversionService = (FormattingConversionService) registry;
DomainClassConverter<FormattingConversionService> converter = new DomainClassConverter<FormattingConversionService>(
conversionService);
@ -94,5 +96,7 @@ public class SpringDataWebConfiguration extends WebMvcConfigurerAdapter { @@ -94,5 +96,7 @@ public class SpringDataWebConfiguration extends WebMvcConfigurerAdapter {
argumentResolvers.add(sortResolver());
argumentResolvers.add(pageableResolver());
argumentResolvers.add(new ProxyingHandlerMethodArgumentResolver(conversionService.getObject()));
}
}

2
src/test/java/org/springframework/data/projection/MapAccessingMethodInterceptorUnitTests.java

@ -120,7 +120,7 @@ public class MapAccessingMethodInterceptorUnitTests { @@ -120,7 +120,7 @@ public class MapAccessingMethodInterceptorUnitTests {
new MapAccessingMethodInterceptor(Collections.<String, Object> emptyMap()).invoke(invocation);
}
public static interface Sample {
interface Sample {
String getName();

142
src/test/java/org/springframework/data/web/MapDataBinderUnitTests.java

@ -0,0 +1,142 @@ @@ -0,0 +1,142 @@
/*
* Copyright 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.data.web;
import static java.util.Collections.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.format.support.DefaultFormattingConversionService;
/**
* Unit tests for {@link MapDataBinder}.
*
* @author Oliver Gierke
*/
public class MapDataBinderUnitTests {
/**
* @see DATACMNS-630
*/
@Test
public void honorsFormattingAnnotationOnAccessor() {
Date reference = new Date();
MutablePropertyValues values = new MutablePropertyValues();
values.add("foo.date", new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(reference));
Map<String, Object> nested = new HashMap<String, Object>();
nested.put("date", reference);
assertThat(bind(values), hasEntry("foo", (Object) nested));
}
/**
* @see DATACMNS-630
*/
@Test
@SuppressWarnings("rawtypes")
public void bindsNestedCollectionElement() {
MutablePropertyValues values = new MutablePropertyValues();
values.add("foo.bar.fooBar[0]", "String");
Map<String, Object> result = bind(values);
List<String> list = new ArrayList<String>();
list.add("String");
assertThat(result, is((Map) singletonMap("foo", singletonMap("bar", singletonMap("fooBar", list)))));
}
/**
* @see DATACMNS-630
*/
@Test
@SuppressWarnings("rawtypes")
public void bindsNestedPrimitive() {
MutablePropertyValues values = new MutablePropertyValues();
values.add("foo.firstname", "Dave");
values.add("foo.lastname", "Matthews");
Map<String, Object> result = bind(values);
Map<String, Object> dave = new HashMap<String, Object>();
dave.put("firstname", "Dave");
dave.put("lastname", "Matthews");
assertThat(result, is((Map) singletonMap("foo", dave)));
}
/**
* @see DATACMNS-630
*/
@Test
public void skipsPropertyNotExposedByTheTypeHierarchy() {
MutablePropertyValues values = new MutablePropertyValues();
values.add("somethingWeird", "Value");
assertThat(bind(values), is(Collections.<String, Object> emptyMap()));
}
private static Map<String, Object> bind(PropertyValues values) {
MapDataBinder binder = new MapDataBinder(Root.class, new DefaultFormattingConversionService());
binder.bind(values);
return binder.getTarget();
}
interface Root {
Foo getFoo();
Bar getBar();
}
interface Foo {
Bar getBar();
String getLastname();
String getFirstname();
@DateTimeFormat(iso = ISO.DATE_TIME)
Date getDate();
}
interface Bar {
Collection<String> getFooBar();
}
}

31
src/test/java/org/springframework/data/web/config/EnableSpringDataWebSupportIntegrationTests.java

@ -17,6 +17,8 @@ package org.springframework.data.web.config; @@ -17,6 +17,8 @@ package org.springframework.data.web.config;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import java.lang.reflect.Field;
import java.util.ArrayList;
@ -27,6 +29,7 @@ import org.hamcrest.Matcher; @@ -27,6 +29,7 @@ import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.geo.Distance;
@ -36,10 +39,14 @@ import org.springframework.data.web.PagedResourcesAssemblerArgumentResolver; @@ -36,10 +39,14 @@ import org.springframework.data.web.PagedResourcesAssemblerArgumentResolver;
import org.springframework.data.web.SortHandlerMethodArgumentResolver;
import org.springframework.data.web.WebTestUtils;
import org.springframework.data.web.config.EnableSpringDataWebSupport.SpringDataWebConfigurationImportSelector;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.util.UriComponentsBuilder;
/**
* Integration tests for {@link EnableSpringDataWebSupport}.
@ -57,6 +64,9 @@ public class EnableSpringDataWebSupportIntegrationTests { @@ -57,6 +64,9 @@ public class EnableSpringDataWebSupportIntegrationTests {
@EnableSpringDataWebSupport
static class SampleConfig {
public @Bean SampleController controller() {
return new SampleController();
}
}
@After
@ -141,6 +151,27 @@ public class EnableSpringDataWebSupportIntegrationTests { @@ -141,6 +151,27 @@ public class EnableSpringDataWebSupportIntegrationTests {
assertThat(conversionService.canConvert(Point.class, String.class), is(true));
}
/**
* @see DATACMNS-630
*/
@Test
public void createsProxyForInterfaceBasedControllerMethodParameter() throws Exception {
WebApplicationContext applicationContext = WebTestUtils.createApplicationContext(SampleConfig.class);
MockMvc mvc = MockMvcBuilders.webAppContextSetup(applicationContext).build();
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("/proxy");
builder.queryParam("name", "Foo");
builder.queryParam("shippingAddresses[0].zipCode", "ZIP");
builder.queryParam("shippingAddresses[0].city", "City");
builder.queryParam("billingAddress.zipCode", "ZIP");
builder.queryParam("billingAddress.city", "City");
builder.queryParam("date", "2014-01-11");
mvc.perform(post(builder.build().toString())).//
andExpect(status().isOk());
}
@SuppressWarnings("unchecked")
private static void assertResolversRegistered(ApplicationContext context, Class<?>... resolverTypes) {

74
src/test/java/org/springframework/data/web/config/SampleController.java

@ -0,0 +1,74 @@ @@ -0,0 +1,74 @@
/*
* Copyright 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.data.web.config;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.Collection;
import java.util.Date;
import org.springframework.data.web.config.SampleController.SampleDto.Address;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @author Oliver Gierke
*/
@Controller
public class SampleController {
@RequestMapping("/proxy")
public String someMethod(SampleDto sampleDto) {
assertThat(sampleDto, is(notNullValue()));
assertThat(sampleDto.getName(), is("Foo"));
assertThat(sampleDto.getDate(), is(notNullValue()));
Collection<Address> shippingAddresses = sampleDto.getShippingAddresses();
assertThat(shippingAddresses, is(hasSize(1)));
assertThat(shippingAddresses.iterator().next().getZipCode(), is("ZIP"));
assertThat(shippingAddresses.iterator().next().getCity(), is("City"));
assertThat(sampleDto.getBillingAddress(), is(notNullValue()));
assertThat(sampleDto.getBillingAddress().getZipCode(), is("ZIP"));
assertThat(sampleDto.getBillingAddress().getCity(), is("City"));
return "view";
}
interface SampleDto {
String getName();
@DateTimeFormat(iso = ISO.DATE)
Date getDate();
Address getBillingAddress();
Collection<Address> getShippingAddresses();
interface Address {
String getZipCode();
String getCity();
}
}
}
Loading…
Cancel
Save