initial JSR-303 Bean Validation support; revised ConversionService and FormatterRegistry
This commit is contained in:
@@ -1,9 +1,21 @@
|
||||
/*
|
||||
* Copyright 2002-2009 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.ui.format;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
@@ -13,14 +25,20 @@ import java.math.BigInteger;
|
||||
import java.text.ParseException;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.ui.format.number.CurrencyFormatter;
|
||||
import org.springframework.ui.format.number.IntegerFormatter;
|
||||
import org.springframework.ui.format.support.GenericFormatterRegistry;
|
||||
|
||||
/**
|
||||
* @author Keith Donald
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
public class GenericFormatterRegistryTests {
|
||||
|
||||
private GenericFormatterRegistry registry;
|
||||
@@ -32,8 +50,8 @@ public class GenericFormatterRegistryTests {
|
||||
|
||||
@Test
|
||||
public void testAdd() throws ParseException {
|
||||
registry.add(new IntegerFormatter());
|
||||
Formatter formatter = registry.getFormatter(typeDescriptor(Integer.class));
|
||||
registry.addFormatterByType(new IntegerFormatter());
|
||||
Formatter formatter = registry.getFormatter(Integer.class);
|
||||
String formatted = formatter.format(new Integer(3), Locale.US);
|
||||
assertEquals("3", formatted);
|
||||
Integer i = (Integer) formatter.parse("3", Locale.US);
|
||||
@@ -42,23 +60,38 @@ public class GenericFormatterRegistryTests {
|
||||
|
||||
@Test
|
||||
public void testAddByObjectType() {
|
||||
registry.add(BigInteger.class, new IntegerFormatter());
|
||||
Formatter formatter = registry.getFormatter(typeDescriptor(BigInteger.class));
|
||||
registry.addFormatterByType(BigInteger.class, new IntegerFormatter());
|
||||
Formatter formatter = registry.getFormatter(BigInteger.class);
|
||||
String formatted = formatter.format(new BigInteger("3"), Locale.US);
|
||||
assertEquals("3", formatted);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddAnnotationFormatterFactory() throws Exception {
|
||||
registry.add(new CurrencyAnnotationFormatterFactory());
|
||||
public void testAddByAnnotation() throws Exception {
|
||||
registry.addFormatterByAnnotation(Currency.class, new CurrencyFormatter());
|
||||
Formatter formatter = registry.getFormatter(new TypeDescriptor(getClass().getField("currencyField")));
|
||||
String formatted = formatter.format(new BigDecimal("5.00"), Locale.US);
|
||||
assertEquals("$5.00", formatted);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddAnnotationFormatterFactory() throws Exception {
|
||||
registry.addFormatterByAnnotation(new CurrencyAnnotationFormatterFactory());
|
||||
Formatter formatter = registry.getFormatter(new TypeDescriptor(getClass().getField("currencyField")));
|
||||
String formatted = formatter.format(new BigDecimal("5.00"), Locale.US);
|
||||
assertEquals("$5.00", formatted);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDefaultFormatterFromMetaAnnotation() throws Exception {
|
||||
Formatter formatter = registry.getFormatter(new TypeDescriptor(getClass().getField("smartCurrencyField")));
|
||||
String formatted = formatter.format(new BigDecimal("5.00"), Locale.US);
|
||||
assertEquals("$5.00", formatted);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDefaultFormatterForType() {
|
||||
Formatter formatter = registry.getFormatter(typeDescriptor(Address.class));
|
||||
Formatter formatter = registry.getFormatter(Address.class);
|
||||
Address address = new Address();
|
||||
address.street = "12345 Bel Aire Estates";
|
||||
address.city = "Palm Bay";
|
||||
@@ -70,33 +103,44 @@ public class GenericFormatterRegistryTests {
|
||||
|
||||
@Test
|
||||
public void testGetNoFormatterForType() {
|
||||
assertNull(registry.getFormatter(typeDescriptor(Integer.class)));
|
||||
assertNull(registry.getFormatter(Integer.class));
|
||||
}
|
||||
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void testGetFormatterCannotConvert() {
|
||||
registry.add(Integer.class, new AddressFormatter());
|
||||
registry.addFormatterByType(Integer.class, new AddressFormatter());
|
||||
}
|
||||
|
||||
|
||||
@Currency
|
||||
public BigDecimal currencyField;
|
||||
|
||||
private static TypeDescriptor typeDescriptor(Class<?> clazz) {
|
||||
return TypeDescriptor.valueOf(clazz);
|
||||
@SmartCurrency
|
||||
public BigDecimal smartCurrencyField;
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Currency {
|
||||
}
|
||||
|
||||
public static class CurrencyAnnotationFormatterFactory implements
|
||||
AnnotationFormatterFactory<Currency, BigDecimal> {
|
||||
@Target({ElementType.METHOD, ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Formatted(CurrencyFormatter.class)
|
||||
public @interface SmartCurrency {
|
||||
}
|
||||
|
||||
public static class CurrencyAnnotationFormatterFactory implements AnnotationFormatterFactory<Currency, Number> {
|
||||
|
||||
private CurrencyFormatter currencyFormatter = new CurrencyFormatter();
|
||||
private final CurrencyFormatter currencyFormatter = new CurrencyFormatter();
|
||||
|
||||
public Formatter<BigDecimal> getFormatter(Currency annotation) {
|
||||
return currencyFormatter;
|
||||
public Formatter<Number> getFormatter(Currency annotation) {
|
||||
return this.currencyFormatter;
|
||||
}
|
||||
}
|
||||
|
||||
@Formatted(AddressFormatter.class)
|
||||
public static class Address {
|
||||
|
||||
private String street;
|
||||
private String city;
|
||||
private String state;
|
||||
@@ -148,7 +192,7 @@ public class GenericFormatterRegistryTests {
|
||||
.append("zip", zip).toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class AddressFormatter implements Formatter<Address> {
|
||||
|
||||
public String format(Address address, Locale locale) {
|
||||
@@ -164,14 +208,6 @@ public class GenericFormatterRegistryTests {
|
||||
address.setZip(fields[3]);
|
||||
return address;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Target( { ElementType.METHOD, ElementType.FIELD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface Currency {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Target;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
@@ -44,6 +49,8 @@ import org.springframework.context.support.ResourceBundleMessageSource;
|
||||
import org.springframework.context.support.StaticMessageSource;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.ui.format.number.DecimalFormatter;
|
||||
import org.springframework.ui.format.Formatted;
|
||||
import org.springframework.ui.format.support.GenericFormatterRegistry;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -297,7 +304,7 @@ public class DataBinderTests extends TestCase {
|
||||
public void testBindingWithFormatter() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
binder.getFormatterRegistry().add(Float.class, new DecimalFormatter());
|
||||
binder.getFormatterRegistry().addFormatterByType(Float.class, new DecimalFormatter());
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
pvs.addPropertyValue("myFloat", "1,2");
|
||||
|
||||
@@ -322,6 +329,45 @@ public class DataBinderTests extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public void testBindingWithDefaultFormatterFromField() {
|
||||
doTestBindingWithDefaultFormatter(new FormattedFieldTestBean());
|
||||
}
|
||||
|
||||
public void testBindingWithDefaultFormatterFromGetter() {
|
||||
doTestBindingWithDefaultFormatter(new FormattedGetterTestBean());
|
||||
}
|
||||
|
||||
public void testBindingWithDefaultFormatterFromSetter() {
|
||||
doTestBindingWithDefaultFormatter(new FormattedSetterTestBean());
|
||||
}
|
||||
|
||||
private void doTestBindingWithDefaultFormatter(Object tb) {
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
binder.setFormatterRegistry(new GenericFormatterRegistry());
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
pvs.addPropertyValue("number", "1,2");
|
||||
|
||||
LocaleContextHolder.setLocale(Locale.GERMAN);
|
||||
try {
|
||||
binder.bind(pvs);
|
||||
assertEquals(new Float("1.2"), binder.getBindingResult().getRawFieldValue("number"));
|
||||
assertEquals("1,2", binder.getBindingResult().getFieldValue("number"));
|
||||
|
||||
PropertyEditor editor = binder.getBindingResult().findEditor("number", Float.class);
|
||||
assertNotNull(editor);
|
||||
editor.setValue(new Float("1.4"));
|
||||
assertEquals("1,4", editor.getAsText());
|
||||
|
||||
editor = binder.getBindingResult().findEditor("number", null);
|
||||
assertNotNull(editor);
|
||||
editor.setAsText("1,6");
|
||||
assertEquals(new Float("1.6"), editor.getValue());
|
||||
}
|
||||
finally {
|
||||
LocaleContextHolder.resetLocaleContext();
|
||||
}
|
||||
}
|
||||
|
||||
public void testBindingWithAllowedFields() throws Exception {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod);
|
||||
@@ -1376,4 +1422,56 @@ public class DataBinderTests extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Formatted(DecimalFormatter.class)
|
||||
public @interface Decimal {
|
||||
}
|
||||
|
||||
|
||||
private static class FormattedFieldTestBean {
|
||||
|
||||
@Decimal
|
||||
private Float number;
|
||||
|
||||
public Float getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setNumber(Float number) {
|
||||
this.number = number;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class FormattedGetterTestBean {
|
||||
|
||||
private Float number;
|
||||
|
||||
@Decimal
|
||||
public Float getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setNumber(Float number) {
|
||||
this.number = number;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class FormattedSetterTestBean {
|
||||
|
||||
private Float number;
|
||||
|
||||
public Float getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
@Decimal
|
||||
public void setNumber(Float number) {
|
||||
this.number = number;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright 2002-2009 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.validation.beanvalidation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.ConstraintPayload;
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.hibernate.validation.HibernateValidationProvider;
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.validation.BeanPropertyBindingResult;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.validation.ObjectError;
|
||||
|
||||
/**
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
*/
|
||||
public class ValidatorFactoryTests {
|
||||
|
||||
@Test
|
||||
public void testSimpleValidation() throws Exception {
|
||||
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||
validator.afterPropertiesSet();
|
||||
ValidPerson person = new ValidPerson();
|
||||
Set<ConstraintViolation<ValidPerson>> result = validator.validate(person);
|
||||
assertEquals(2, result.size());
|
||||
for (ConstraintViolation<ValidPerson> cv : result) {
|
||||
String path = cv.getPropertyPath().toString();
|
||||
if ("name".equals(path) || "address.street".equals(path)) {
|
||||
assertTrue(cv.getConstraintDescriptor().getAnnotation() instanceof NotNull);
|
||||
}
|
||||
else {
|
||||
fail("Invalid constraint violation with path '" + path + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleValidationWithCustomProvider() throws Exception {
|
||||
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||
validator.setProviderClass(HibernateValidationProvider.class);
|
||||
validator.afterPropertiesSet();
|
||||
ValidPerson person = new ValidPerson();
|
||||
Set<ConstraintViolation<ValidPerson>> result = validator.validate(person);
|
||||
assertEquals(2, result.size());
|
||||
for (ConstraintViolation<ValidPerson> cv : result) {
|
||||
String path = cv.getPropertyPath().toString();
|
||||
if ("name".equals(path) || "address.street".equals(path)) {
|
||||
assertTrue(cv.getConstraintDescriptor().getAnnotation() instanceof NotNull);
|
||||
}
|
||||
else {
|
||||
fail("Invalid constraint violation with path '" + path + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleValidationWithClassLevel() throws Exception {
|
||||
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||
validator.afterPropertiesSet();
|
||||
ValidPerson person = new ValidPerson();
|
||||
person.setName("Juergen");
|
||||
person.getAddress().setStreet("Juergen's Street");
|
||||
Set<ConstraintViolation<ValidPerson>> result = validator.validate(person);
|
||||
assertEquals(1, result.size());
|
||||
Iterator<ConstraintViolation<ValidPerson>> iterator = result.iterator();
|
||||
ConstraintViolation cv = iterator.next();
|
||||
assertEquals("", cv.getPropertyPath().toString());
|
||||
assertTrue(cv.getConstraintDescriptor().getAnnotation() instanceof NameAddressValid);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpringValidation() throws Exception {
|
||||
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||
validator.afterPropertiesSet();
|
||||
ValidPerson person = new ValidPerson();
|
||||
BeanPropertyBindingResult result = new BeanPropertyBindingResult(person, "person");
|
||||
validator.validate(person, result);
|
||||
assertEquals(2, result.getErrorCount());
|
||||
FieldError fieldError = result.getFieldError("name");
|
||||
assertEquals("name", fieldError.getField());
|
||||
System.out.println(Arrays.asList(fieldError.getCodes()));
|
||||
System.out.println(fieldError.getDefaultMessage());
|
||||
fieldError = result.getFieldError("address.street");
|
||||
assertEquals("address.street", fieldError.getField());
|
||||
System.out.println(Arrays.asList(fieldError.getCodes()));
|
||||
System.out.println(fieldError.getDefaultMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpringValidationWithClassLevel() throws Exception {
|
||||
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||
validator.afterPropertiesSet();
|
||||
ValidPerson person = new ValidPerson();
|
||||
person.setName("Juergen");
|
||||
person.getAddress().setStreet("Juergen's Street");
|
||||
BeanPropertyBindingResult result = new BeanPropertyBindingResult(person, "person");
|
||||
validator.validate(person, result);
|
||||
assertEquals(1, result.getErrorCount());
|
||||
ObjectError fieldError = result.getGlobalError();
|
||||
System.out.println(Arrays.asList(fieldError.getCodes()));
|
||||
System.out.println(fieldError.getDefaultMessage());
|
||||
}
|
||||
|
||||
|
||||
@NameAddressValid
|
||||
private static class ValidPerson {
|
||||
|
||||
@NotNull
|
||||
private String name;
|
||||
|
||||
@Valid
|
||||
private ValidAddress address = new ValidAddress();
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public ValidAddress getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(ValidAddress address) {
|
||||
this.address = address;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class ValidAddress {
|
||||
|
||||
@NotNull
|
||||
private String street;
|
||||
|
||||
public String getStreet() {
|
||||
return street;
|
||||
}
|
||||
|
||||
public void setStreet(String street) {
|
||||
this.street = street;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Constraint(validatedBy = NameAddressValidator.class)
|
||||
public @interface NameAddressValid {
|
||||
|
||||
String message() default "Street must not contain name";
|
||||
|
||||
Class<?>[] groups() default {};
|
||||
|
||||
Class<? extends ConstraintPayload>[] payload() default {};
|
||||
}
|
||||
|
||||
|
||||
public static class NameAddressValidator implements ConstraintValidator<NameAddressValid, ValidPerson> {
|
||||
|
||||
public void initialize(NameAddressValid constraintAnnotation) {
|
||||
}
|
||||
|
||||
public boolean isValid(ValidPerson value, ConstraintValidatorContext constraintValidatorContext) {
|
||||
return (value.name == null || !value.address.street.contains(value.name));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user