initial JSR-303 Bean Validation support; revised ConversionService and FormatterRegistry
This commit is contained in:
@@ -27,8 +27,8 @@ import java.lang.annotation.Annotation;
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
* @param <A> The type of Annotation this factory uses to create Formatter instances
|
||||
* @param <T> The type of Object Formatters created by this factory format
|
||||
* @param <A> the type of Annotation this factory uses to create Formatter instances
|
||||
* @param <T> the type of object that the factory's Formatters are dealing with
|
||||
*/
|
||||
public interface AnnotationFormatterFactory<A extends Annotation, T> {
|
||||
|
||||
|
||||
@@ -36,6 +36,6 @@ public @interface Formatted {
|
||||
/**
|
||||
* The Formatter that handles the formatting for the annotated element.
|
||||
*/
|
||||
Class<?> value();
|
||||
Class<? extends Formatter> value();
|
||||
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import java.util.Locale;
|
||||
* @param <T> the type of object this formatter can format
|
||||
*/
|
||||
public interface Formatter<T> {
|
||||
|
||||
|
||||
/**
|
||||
* Format the object of type T for display.
|
||||
* @param object the object to format
|
||||
@@ -35,13 +35,15 @@ public interface Formatter<T> {
|
||||
* @return the formatted display string
|
||||
*/
|
||||
String format(T object, Locale locale);
|
||||
|
||||
|
||||
/**
|
||||
* Parse an object from its formatted representation.
|
||||
* @param formatted a formatted representation
|
||||
* @param locale the user's locale
|
||||
* @return the parsed object
|
||||
* @throws ParseException when a parse exception occurs
|
||||
* @throws RuntimeException when thrown by coercion methods that are
|
||||
*
|
||||
*/
|
||||
T parse(String formatted, Locale locale) throws ParseException;
|
||||
|
||||
|
||||
@@ -28,30 +28,52 @@ import org.springframework.core.convert.TypeDescriptor;
|
||||
*/
|
||||
public interface FormatterRegistry {
|
||||
|
||||
/**
|
||||
* Adds a Formatter to this registry indexed by <T>.
|
||||
* Calling getFormatter(<T>.class) returns <code>formatter</code>.
|
||||
* @param formatter the formatter
|
||||
* @param <T> the type of object the formatter formats
|
||||
*/
|
||||
<T> void add(Formatter<T> formatter);
|
||||
|
||||
/**
|
||||
* Adds a Formatter to this registry indexed by type.
|
||||
* Use this add method when type differs from <T>.
|
||||
* Calling getFormatter(type) returns a decorator that wraps the targetFormatter.
|
||||
* On format, the decorator first coerses the instance of type to <T>, then delegates to <code>targetFormatter</code> to format the value.
|
||||
* On parse, the decorator first delegates to the formatter to parse a <T>, then coerses the parsed value to type.
|
||||
* <p>Use this add method when type differs from <T>.
|
||||
* Calling <code>getFormatter(type)</code> returns a decorator that wraps
|
||||
* the <code>targetFormatter</code> instance.
|
||||
* <p>On format, the decorator first coerses the instance of type to <T>,
|
||||
* then delegates to <code>targetFormatter</code> to format the value.
|
||||
* <p>On parse, the decorator first delegates to the formatter to parse a <T>,
|
||||
* then coerces the parsed value to type.
|
||||
* @param type the object type
|
||||
* @param targetFormatter the target formatter
|
||||
*/
|
||||
void add(Class<?> type, Formatter<?> targetFormatter);
|
||||
void addFormatterByType(Class<?> type, Formatter<?> targetFormatter);
|
||||
|
||||
/**
|
||||
* Adds a Formatter to this registry indexed by <T>.
|
||||
* <o>Calling <code>getFormatter(<T>.class)</code> returns <code>formatter</code>.
|
||||
* @param formatter the formatter
|
||||
* @param <T> the type of object the formatter formats
|
||||
*/
|
||||
<T> void addFormatterByType(Formatter<T> formatter);
|
||||
|
||||
/**
|
||||
* Adds a Formatter to this registry indexed by the given annotation type.
|
||||
* <o>Calling <code>getFormatter(...)</code> on a field or accessor method
|
||||
* with the given annotation returns <code>formatter</code>.
|
||||
* @param formatter the formatter
|
||||
* @param <T> the type of object the formatter formats
|
||||
*/
|
||||
void addFormatterByAnnotation(Class<? extends Annotation> annotationType, Formatter<?> formatter);
|
||||
|
||||
/**
|
||||
* Adds a AnnotationFormatterFactory that returns the Formatter for properties annotated with a specific annotation.
|
||||
* <o>Calling <code>getFormatter(...)</code> on a field or accessor method
|
||||
* with the given annotation returns <code>formatter</code>.
|
||||
* @param factory the annotation formatter factory
|
||||
* @param <A> the type of Annotation this factory uses to create Formatter instances
|
||||
* @param <T> the type of object that the factory's Formatters are dealing with
|
||||
*/
|
||||
void add(AnnotationFormatterFactory<?, ?> factory);
|
||||
<A extends Annotation, T> void addFormatterByAnnotation(AnnotationFormatterFactory<A, T> factory);
|
||||
|
||||
/**
|
||||
* Get the Formatter for the specified type.
|
||||
* @return the Formatter, or <code>null</code> if no suitable one is registered
|
||||
*/
|
||||
<T> Formatter<T> getFormatter(Class<T> targetType);
|
||||
|
||||
/**
|
||||
* Get the Formatter for the type descriptor.
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.springframework.ui.format.Formatter;
|
||||
|
||||
@@ -33,12 +34,16 @@ import org.springframework.ui.format.Formatter;
|
||||
* @since 3.0
|
||||
* @see SimpleDateFormat
|
||||
*/
|
||||
public final class DateFormatter implements Formatter<Date> {
|
||||
public class DateFormatter implements Formatter<Date> {
|
||||
|
||||
private String pattern;
|
||||
|
||||
private int style = DateFormat.DEFAULT;
|
||||
|
||||
private TimeZone timeZone;
|
||||
|
||||
private boolean lenient = false;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new default DateFormatter.
|
||||
@@ -75,6 +80,22 @@ public final class DateFormatter implements Formatter<Date> {
|
||||
this.style = style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the TimeZone to normalize the date values into, if any.
|
||||
*/
|
||||
public void setTimeZone(TimeZone timeZone) {
|
||||
this.timeZone = timeZone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether or not parsing is to be lenient. Default is false.
|
||||
* <p>With lenient parsing, the parser may allow inputs that do not precisely match the format.
|
||||
* With strict parsing, inputs must match the format exactly.
|
||||
*/
|
||||
public void setLenient(boolean lenient) {
|
||||
this.lenient = lenient;
|
||||
}
|
||||
|
||||
|
||||
public String format(Date date, Locale locale) {
|
||||
if (date == null) {
|
||||
@@ -99,7 +120,10 @@ public final class DateFormatter implements Formatter<Date> {
|
||||
else {
|
||||
dateFormat = DateFormat.getDateInstance(this.style, locale);
|
||||
}
|
||||
dateFormat.setLenient(false);
|
||||
if (this.timeZone != null) {
|
||||
dateFormat.setTimeZone(this.timeZone);
|
||||
}
|
||||
dateFormat.setLenient(this.lenient);
|
||||
return dateFormat;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
/**
|
||||
* Formatters for <code>java.util.Date</code> fields.
|
||||
* Formatters for <code>java.util.Date</code> properties.
|
||||
*/
|
||||
package org.springframework.ui.format.date;
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.number;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.ui.format.Formatter;
|
||||
|
||||
/**
|
||||
* Abstract formatter for Numbers,
|
||||
* providing a {@link #getNumberFormat(java.util.Locale)} template method.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
*/
|
||||
public abstract class AbstractNumberFormatter implements Formatter<Number> {
|
||||
|
||||
private boolean lenient = false;
|
||||
|
||||
|
||||
/**
|
||||
* Specify whether or not parsing is to be lenient. Default is false.
|
||||
* <p>With lenient parsing, the parser may allow inputs that do not precisely match the format.
|
||||
* With strict parsing, inputs must match the format exactly.
|
||||
*/
|
||||
public void setLenient(boolean lenient) {
|
||||
this.lenient = lenient;
|
||||
}
|
||||
|
||||
|
||||
public String format(Number integer, Locale locale) {
|
||||
if (integer == null) {
|
||||
return "";
|
||||
}
|
||||
NumberFormat format = getNumberFormat(locale);
|
||||
return format.format(integer);
|
||||
}
|
||||
|
||||
public Number parse(String formatted, Locale locale) throws ParseException {
|
||||
if (formatted.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
NumberFormat format = getNumberFormat(locale);
|
||||
ParsePosition position = new ParsePosition(0);
|
||||
Number number = format.parse(formatted, position);
|
||||
if (position.getErrorIndex() != -1) {
|
||||
throw new ParseException(formatted, position.getIndex());
|
||||
}
|
||||
if (!this.lenient) {
|
||||
if (formatted.length() != position.getIndex()) {
|
||||
// indicates a part of the string that was not parsed
|
||||
throw new ParseException(formatted, position.getIndex());
|
||||
}
|
||||
}
|
||||
return number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a concrete NumberFormat for the specified locale.
|
||||
* @param locale the current locale
|
||||
* @return the NumberFormat instance (never <code>null</code>)
|
||||
*/
|
||||
protected abstract NumberFormat getNumberFormat(Locale locale);
|
||||
|
||||
}
|
||||
@@ -1,82 +1,103 @@
|
||||
/*
|
||||
* Copyright 2004-2009 the original author or authors.
|
||||
*
|
||||
* 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.number;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.Currency;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.ui.format.Formatter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* A BigDecimal formatter for currency values.
|
||||
* Delegates to {@link NumberFormat#getCurrencyInstance(Locale)}.
|
||||
*
|
||||
* <p>Delegates to {@link NumberFormat#getCurrencyInstance(Locale)}.
|
||||
* Configures BigDecimal parsing so there is no loss of precision.
|
||||
* Sets the scale of parsed BigDecimal values to {@link NumberFormat#getMaximumFractionDigits()}.
|
||||
* Applies {@link RoundingMode#DOWN} to parsed values.
|
||||
* Can apply a specified {@link RoundingMode} to parsed values.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see #setLenient(boolean)
|
||||
* @see #setLenient
|
||||
* @see #setRoundingMode
|
||||
*/
|
||||
public final class CurrencyFormatter implements Formatter<BigDecimal> {
|
||||
public final class CurrencyFormatter extends AbstractNumberFormatter {
|
||||
|
||||
private CurrencyNumberFormatFactory currencyFormatFactory = new CurrencyNumberFormatFactory();
|
||||
private static final boolean roundingModeOnDecimalFormat =
|
||||
ClassUtils.hasMethod(DecimalFormat.class, "setRoundingMode", RoundingMode.class);
|
||||
|
||||
private int fractionDigits = 2;
|
||||
|
||||
private RoundingMode roundingMode;
|
||||
|
||||
private Currency currency;
|
||||
|
||||
private boolean lenient;
|
||||
|
||||
/**
|
||||
* Specify whether or not parsing is to be lenient.
|
||||
* With lenient parsing, the parser may allow inputs that do not precisely match the format.
|
||||
* With strict parsing, inputs must match the format exactly.
|
||||
* Default is false.
|
||||
* Specify the desired number of fraction digits.
|
||||
* Default is 2.
|
||||
*/
|
||||
public void setLenient(boolean lenient) {
|
||||
this.lenient = lenient;
|
||||
public void setFractionDigits(int fractionDigits) {
|
||||
this.fractionDigits = fractionDigits;
|
||||
}
|
||||
|
||||
public String format(BigDecimal decimal, Locale locale) {
|
||||
if (decimal == null) {
|
||||
return "";
|
||||
}
|
||||
NumberFormat format = currencyFormatFactory.getNumberFormat(locale);
|
||||
return format.format(decimal);
|
||||
/**
|
||||
* Specify the rounding mode to use for decimal parsing.
|
||||
* Default is {@link RoundingMode#UNNECESSARY}.
|
||||
*/
|
||||
public void setRoundingMode(RoundingMode roundingMode) {
|
||||
this.roundingMode = roundingMode;
|
||||
}
|
||||
|
||||
public BigDecimal parse(String formatted, Locale locale)
|
||||
throws ParseException {
|
||||
if (formatted.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
NumberFormat format = currencyFormatFactory.getNumberFormat(locale);
|
||||
ParsePosition position = new ParsePosition(0);
|
||||
BigDecimal decimal = (BigDecimal) format.parse(formatted, position);
|
||||
if (position.getErrorIndex() != -1) {
|
||||
throw new ParseException(formatted, position.getIndex());
|
||||
/**
|
||||
* Specify the currency, if known.
|
||||
*/
|
||||
public void setCurrency(Currency currency) {
|
||||
this.currency = currency;
|
||||
}
|
||||
|
||||
|
||||
public BigDecimal parse(String formatted, Locale locale) throws ParseException {
|
||||
BigDecimal decimal = (BigDecimal) super.parse(formatted, locale);
|
||||
if (this.roundingMode != null) {
|
||||
decimal = decimal.setScale(this.fractionDigits, this.roundingMode);
|
||||
}
|
||||
else {
|
||||
decimal = decimal.setScale(this.fractionDigits);
|
||||
}
|
||||
if (!lenient) {
|
||||
if (formatted.length() != position.getIndex()) {
|
||||
// indicates a part of the string that was not parsed
|
||||
throw new ParseException(formatted, position.getIndex());
|
||||
}
|
||||
}
|
||||
decimal = decimal.setScale(format.getMaximumFractionDigits(), format.getRoundingMode());
|
||||
return decimal;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected NumberFormat getNumberFormat(Locale locale) {
|
||||
DecimalFormat format = (DecimalFormat) NumberFormat.getCurrencyInstance(locale);
|
||||
format.setParseBigDecimal(true);
|
||||
format.setMaximumFractionDigits(this.fractionDigits);
|
||||
format.setMinimumFractionDigits(this.fractionDigits);
|
||||
if (this.roundingMode != null && roundingModeOnDecimalFormat) {
|
||||
format.setRoundingMode(this.roundingMode);
|
||||
}
|
||||
if (this.currency != null) {
|
||||
format.setCurrency(this.currency);
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-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.number;
|
||||
|
||||
import java.math.RoundingMode;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Produces NumberFormat instances that format currency values.
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
* @see NumberFormat
|
||||
*/
|
||||
final class CurrencyNumberFormatFactory extends NumberFormatFactory {
|
||||
|
||||
private RoundingMode roundingMode = RoundingMode.DOWN;
|
||||
|
||||
public NumberFormat getNumberFormat(Locale locale) {
|
||||
DecimalFormat format = (DecimalFormat) NumberFormat.getCurrencyInstance(locale);
|
||||
format.setParseBigDecimal(true);
|
||||
format.setRoundingMode(roundingMode);
|
||||
return format;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +1,43 @@
|
||||
/*
|
||||
* Copyright 2004-2009 the original author or authors.
|
||||
*
|
||||
* 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.number;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.ui.format.Formatter;
|
||||
|
||||
/**
|
||||
* A Number formatter for decimal values.
|
||||
* Delegates to {@link NumberFormat#getInstance(Locale)}.
|
||||
*
|
||||
* <p>Delegates to {@link NumberFormat#getInstance(Locale)}.
|
||||
* Configures BigDecimal parsing so there is no loss in precision.
|
||||
* Allows configuration over the decimal number pattern.
|
||||
* The {@link #parse(String, Locale)} routine always returns a BigDecimal.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see #setPattern(String)
|
||||
* @see #setLenient(boolean)
|
||||
* @see #setPattern
|
||||
* @see #setLenient
|
||||
*/
|
||||
public final class DecimalFormatter implements Formatter<Number> {
|
||||
public final class DecimalFormatter extends AbstractNumberFormatter {
|
||||
|
||||
private DefaultNumberFormatFactory formatFactory = new DefaultNumberFormatFactory();
|
||||
private String pattern;
|
||||
|
||||
private boolean lenient;
|
||||
|
||||
public DecimalFormatter() {
|
||||
initDefaults();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the pattern to use to format number values.
|
||||
@@ -52,50 +46,24 @@ public final class DecimalFormatter implements Formatter<Number> {
|
||||
* @see DecimalFormat#applyPattern(String)
|
||||
*/
|
||||
public void setPattern(String pattern) {
|
||||
formatFactory.setPattern(pattern);
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether or not parsing is to be lenient.
|
||||
* With lenient parsing, the parser may allow inputs that do not precisely match the format.
|
||||
* With strict parsing, inputs must match the format exactly.
|
||||
* Default is false.
|
||||
*/
|
||||
public void setLenient(boolean lenient) {
|
||||
this.lenient = lenient;
|
||||
}
|
||||
|
||||
public String format(Number decimal, Locale locale) {
|
||||
if (decimal == null) {
|
||||
return "";
|
||||
}
|
||||
NumberFormat format = formatFactory.getNumberFormat(locale);
|
||||
return format.format(decimal);
|
||||
}
|
||||
|
||||
public Number parse(String formatted, Locale locale) throws ParseException {
|
||||
if (formatted.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
NumberFormat format = formatFactory.getNumberFormat(locale);
|
||||
ParsePosition position = new ParsePosition(0);
|
||||
BigDecimal decimal = (BigDecimal) format.parse(formatted, position);
|
||||
if (position.getErrorIndex() != -1) {
|
||||
throw new ParseException(formatted, position.getIndex());
|
||||
}
|
||||
if (!lenient) {
|
||||
if (formatted.length() != position.getIndex()) {
|
||||
// indicates a part of the string that was not parsed
|
||||
throw new ParseException(formatted, position.getIndex());
|
||||
public NumberFormat getNumberFormat(Locale locale) {
|
||||
NumberFormat format = NumberFormat.getInstance(locale);
|
||||
if (!(format instanceof DecimalFormat)) {
|
||||
if (this.pattern != null) {
|
||||
throw new IllegalStateException("Cannot support pattern for non-DecimalFormat: " + format);
|
||||
}
|
||||
return format;
|
||||
}
|
||||
return decimal;
|
||||
DecimalFormat decimalFormat = (DecimalFormat) format;
|
||||
decimalFormat.setParseBigDecimal(true);
|
||||
if (this.pattern != null) {
|
||||
decimalFormat.applyPattern(this.pattern);
|
||||
}
|
||||
return decimalFormat;
|
||||
}
|
||||
|
||||
// internal helpers
|
||||
|
||||
private void initDefaults() {
|
||||
formatFactory.setParseBigDecimal(true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-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.number;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Works with a general purpose {@link DecimalFormat} instance returned by calling
|
||||
* {@link NumberFormat#getInstance(Locale)} by default.
|
||||
* @author Keith Donald
|
||||
* @see NumberFormat
|
||||
* @see DecimalFormat
|
||||
* @since 3.0
|
||||
*/
|
||||
class DefaultNumberFormatFactory extends NumberFormatFactory {
|
||||
|
||||
private static Log logger = LogFactory.getLog(DefaultNumberFormatFactory.class);
|
||||
|
||||
private String pattern;
|
||||
|
||||
private Boolean parseBigDecimal;
|
||||
|
||||
/**
|
||||
* Sets the pattern to use to format number values.
|
||||
* If not specified, the default DecimalFormat pattern is used.
|
||||
* @param pattern the format pattern
|
||||
* @see DecimalFormat#applyPattern(String)
|
||||
*/
|
||||
public void setPattern(String pattern) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the format should always parse a big decimal.
|
||||
* @param parseBigDecimal the big decimal parse status
|
||||
* @see DecimalFormat#setParseBigDecimal(boolean)
|
||||
*/
|
||||
public void setParseBigDecimal(boolean parseBigDecimal) {
|
||||
this.parseBigDecimal = parseBigDecimal;
|
||||
}
|
||||
|
||||
public NumberFormat getNumberFormat(Locale locale) {
|
||||
NumberFormat format = NumberFormat.getInstance(locale);
|
||||
if (pattern != null) {
|
||||
if (format instanceof DecimalFormat) {
|
||||
((DecimalFormat) format).applyPattern(pattern);
|
||||
} else {
|
||||
logger.warn("Unable to apply format pattern '" + pattern
|
||||
+ "'; Returned NumberFormat is not a DecimalFormat");
|
||||
}
|
||||
}
|
||||
if (parseBigDecimal != null) {
|
||||
if (format instanceof DecimalFormat) {
|
||||
((DecimalFormat) format).setParseBigDecimal(parseBigDecimal);
|
||||
} else {
|
||||
logger.warn("Unable to call setParseBigDecimal; not a DecimalFormat");
|
||||
}
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,77 +1,39 @@
|
||||
/*
|
||||
* Copyright 2004-2009 the original author or authors.
|
||||
*
|
||||
* 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.number;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.ui.format.Formatter;
|
||||
|
||||
/**
|
||||
* A Number formatter for whole integer values.
|
||||
* Delegates to {@link NumberFormat#getIntegerInstance(Locale)}.
|
||||
*
|
||||
* <p>Delegates to {@link NumberFormat#getIntegerInstance(Locale)}.
|
||||
* The {@link #parse(String, Locale)} routine always returns a Long.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see #setLenient(boolean)
|
||||
* @see #setLenient
|
||||
*/
|
||||
public final class IntegerFormatter implements Formatter<Number> {
|
||||
public final class IntegerFormatter extends AbstractNumberFormatter {
|
||||
|
||||
private IntegerNumberFormatFactory formatFactory = new IntegerNumberFormatFactory();
|
||||
|
||||
private boolean lenient;
|
||||
|
||||
/**
|
||||
* Specify whether or not parsing is to be lenient.
|
||||
* With lenient parsing, the parser may allow inputs that do not precisely match the format.
|
||||
* With strict parsing, inputs must match the format exactly.
|
||||
* Default is false.
|
||||
*/
|
||||
public void setLenient(boolean lenient) {
|
||||
this.lenient = lenient;
|
||||
protected NumberFormat getNumberFormat(Locale locale) {
|
||||
return NumberFormat.getIntegerInstance(locale);
|
||||
}
|
||||
|
||||
public String format(Number integer, Locale locale) {
|
||||
if (integer == null) {
|
||||
return "";
|
||||
}
|
||||
NumberFormat format = formatFactory.getNumberFormat(locale);
|
||||
return format.format(integer);
|
||||
}
|
||||
|
||||
public Number parse(String formatted, Locale locale)
|
||||
throws ParseException {
|
||||
if (formatted.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
NumberFormat format = formatFactory.getNumberFormat(locale);
|
||||
ParsePosition position = new ParsePosition(0);
|
||||
Long integer = (Long) format.parse(formatted, position);
|
||||
if (position.getErrorIndex() != -1) {
|
||||
throw new ParseException(formatted, position.getIndex());
|
||||
}
|
||||
if (!lenient) {
|
||||
if (formatted.length() != position.getIndex()) {
|
||||
// indicates a part of the string that was not parsed
|
||||
throw new ParseException(formatted, position.getIndex());
|
||||
}
|
||||
}
|
||||
return integer;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-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.number;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Produces NumberFormat instances that format integer values.
|
||||
* @author Keith Donald
|
||||
* @see NumberFormat
|
||||
* @since 3.0
|
||||
*/
|
||||
final class IntegerNumberFormatFactory extends NumberFormatFactory {
|
||||
public NumberFormat getNumberFormat(Locale locale) {
|
||||
return NumberFormat.getIntegerInstance(locale);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-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.number;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* A factory for {@link NumberFormat} objects.
|
||||
* Conceals the complexity associated with configuring, constructing, and/or caching number format instances.
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
*/
|
||||
abstract class NumberFormatFactory {
|
||||
|
||||
/**
|
||||
* Factory method that returns a fully-configured {@link NumberFormat} instance to use to format an object for
|
||||
* display.
|
||||
* @return the number format
|
||||
*/
|
||||
public abstract NumberFormat getNumberFormat(Locale locale);
|
||||
|
||||
}
|
||||
@@ -1,79 +1,45 @@
|
||||
/*
|
||||
* Copyright 2004-2009 the original author or authors.
|
||||
*
|
||||
* 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.number;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.ui.format.Formatter;
|
||||
|
||||
/**
|
||||
* A Number formatter for percent values.
|
||||
* Delegates to {@link NumberFormat#getPercentInstance(Locale)}.
|
||||
*
|
||||
* <p>Delegates to {@link NumberFormat#getPercentInstance(Locale)}.
|
||||
* Configures BigDecimal parsing so there is no loss in precision.
|
||||
* The {@link #parse(String, Locale)} routine always returns a BigDecimal.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see #setLenient(boolean)
|
||||
* @see #setLenient
|
||||
*/
|
||||
public final class PercentFormatter implements Formatter<Number> {
|
||||
public final class PercentFormatter extends AbstractNumberFormatter {
|
||||
|
||||
private PercentNumberFormatFactory percentFormatFactory = new PercentNumberFormatFactory();
|
||||
|
||||
private boolean lenient;
|
||||
|
||||
/**
|
||||
* Specify whether or not parsing is to be lenient.
|
||||
* With lenient parsing, the parser may allow inputs that do not precisely match the format.
|
||||
* With strict parsing, inputs must match the format exactly.
|
||||
* Default is false.
|
||||
*/
|
||||
public void setLenient(boolean lenient) {
|
||||
this.lenient = lenient;
|
||||
protected NumberFormat getNumberFormat(Locale locale) {
|
||||
NumberFormat format = NumberFormat.getPercentInstance(locale);
|
||||
if (format instanceof DecimalFormat) {
|
||||
((DecimalFormat) format).setParseBigDecimal(true);
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
public String format(Number number, Locale locale) {
|
||||
if (number == null) {
|
||||
return "";
|
||||
}
|
||||
NumberFormat format = percentFormatFactory.getNumberFormat(locale);
|
||||
return format.format(number);
|
||||
}
|
||||
|
||||
public Number parse(String formatted, Locale locale)
|
||||
throws ParseException {
|
||||
if (formatted.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
NumberFormat format = percentFormatFactory.getNumberFormat(locale);
|
||||
ParsePosition position = new ParsePosition(0);
|
||||
BigDecimal decimal = (BigDecimal) format.parse(formatted, position);
|
||||
if (position.getErrorIndex() != -1) {
|
||||
throw new ParseException(formatted, position.getIndex());
|
||||
}
|
||||
if (!lenient) {
|
||||
if (formatted.length() != position.getIndex()) {
|
||||
// indicates a part of the string that was not parsed
|
||||
throw new ParseException(formatted, position.getIndex());
|
||||
}
|
||||
}
|
||||
return decimal;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-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.number;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Produces NumberFormat instances that format percent values.
|
||||
* @see NumberFormat
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
*/
|
||||
final class PercentNumberFormatFactory extends NumberFormatFactory {
|
||||
public NumberFormat getNumberFormat(Locale locale) {
|
||||
DecimalFormat format = (DecimalFormat) NumberFormat.getPercentInstance(locale);
|
||||
format.setParseBigDecimal(true);
|
||||
return format;
|
||||
}
|
||||
}
|
||||
@@ -2,4 +2,3 @@
|
||||
* Formatters for <code>java.lang.Number</code> properties.
|
||||
*/
|
||||
package org.springframework.ui.format.number;
|
||||
|
||||
|
||||
@@ -16,16 +16,13 @@
|
||||
|
||||
package org.springframework.ui.format.support;
|
||||
|
||||
import java.text.ParseException;
|
||||
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.core.convert.ConversionFailedException;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.ui.format.Formatter;
|
||||
import org.springframework.ui.format.FormatterRegistry;
|
||||
import org.springframework.ui.format.support.GenericFormatterRegistry;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -36,12 +33,10 @@ import org.springframework.util.Assert;
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
*/
|
||||
public class FormattingConversionServiceAdapter implements ConversionService {
|
||||
public class FormattingConversionServiceAdapter extends GenericConversionService {
|
||||
|
||||
private final FormatterRegistry formatterRegistry;
|
||||
|
||||
private final ConversionService targetConversionService;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new FormattingConversionServiceAdapter for the given FormatterRegistry.
|
||||
@@ -51,41 +46,37 @@ public class FormattingConversionServiceAdapter implements ConversionService {
|
||||
Assert.notNull(formatterRegistry, "FormatterRegistry must not be null");
|
||||
this.formatterRegistry = formatterRegistry;
|
||||
if (formatterRegistry instanceof GenericFormatterRegistry) {
|
||||
this.targetConversionService = ((GenericFormatterRegistry) formatterRegistry).getConversionService();
|
||||
setParent(((GenericFormatterRegistry) formatterRegistry).getConversionService());
|
||||
}
|
||||
else {
|
||||
this.targetConversionService = new DefaultConversionService();
|
||||
setParent(new DefaultConversionService());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
|
||||
return canConvert(sourceType, TypeDescriptor.valueOf(targetType));
|
||||
}
|
||||
|
||||
public boolean canConvert(Class<?> sourceType, TypeDescriptor targetType) {
|
||||
return (this.formatterRegistry.getFormatter(targetType) != null ||
|
||||
this.targetConversionService.canConvert(sourceType, targetType));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T convert(Object source, Class<T> targetType) {
|
||||
return (T) convert(source, TypeDescriptor.valueOf(targetType));
|
||||
}
|
||||
|
||||
public Object convert(Object source, TypeDescriptor targetType) {
|
||||
if (source instanceof String) {
|
||||
Formatter formatter = this.formatterRegistry.getFormatter(targetType);
|
||||
@Override
|
||||
protected <T> Converter findRegisteredConverter(Class<?> sourceType, Class<T> targetType) {
|
||||
if (String.class.equals(sourceType)) {
|
||||
Formatter<T> formatter = this.formatterRegistry.getFormatter(targetType);
|
||||
if (formatter != null) {
|
||||
try {
|
||||
return formatter.parse((String) source, LocaleContextHolder.getLocale());
|
||||
}
|
||||
catch (ParseException ex) {
|
||||
throw new ConversionFailedException(source, String.class, targetType.getType(), ex);
|
||||
}
|
||||
return new FormattingConverter<T>(formatter);
|
||||
}
|
||||
}
|
||||
return this.targetConversionService.convert(source, targetType);
|
||||
return super.findRegisteredConverter(sourceType, targetType);
|
||||
}
|
||||
|
||||
|
||||
private static class FormattingConverter<T> implements Converter<String, T> {
|
||||
|
||||
private final Formatter<T> formatter;
|
||||
|
||||
public FormattingConverter(Formatter<T> formatter) {
|
||||
this.formatter = formatter;
|
||||
}
|
||||
|
||||
public T convert(String source) throws Exception {
|
||||
return this.formatter.parse(source, LocaleContextHolder.getLocale());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,22 +27,24 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.ui.format.FormatterRegistry;
|
||||
import org.springframework.ui.format.Formatter;
|
||||
import org.springframework.ui.format.AnnotationFormatterFactory;
|
||||
import org.springframework.ui.format.Formatted;
|
||||
import org.springframework.ui.format.Formatter;
|
||||
import org.springframework.ui.format.FormatterRegistry;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A generic implementation of {@link org.springframework.ui.format.FormatterRegistry} suitable for use in most environments.
|
||||
* A generic implementation of {@link org.springframework.ui.format.FormatterRegistry}
|
||||
* suitable for use in most environments.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Juergen Hoeller
|
||||
@@ -52,7 +54,7 @@ import org.springframework.ui.format.Formatted;
|
||||
* @see #add(Class, org.springframework.ui.format.Formatter)
|
||||
* @see #add(org.springframework.ui.format.AnnotationFormatterFactory)
|
||||
*/
|
||||
public class GenericFormatterRegistry implements FormatterRegistry, BeanFactoryAware, Cloneable {
|
||||
public class GenericFormatterRegistry implements FormatterRegistry, ApplicationContextAware, Cloneable {
|
||||
|
||||
private final Map<Class, Formatter> typeFormatters = new ConcurrentHashMap<Class, Formatter>();
|
||||
|
||||
@@ -61,46 +63,59 @@ public class GenericFormatterRegistry implements FormatterRegistry, BeanFactoryA
|
||||
|
||||
private ConversionService conversionService = new DefaultConversionService();
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private boolean shared = true;
|
||||
|
||||
|
||||
/**
|
||||
* Registers the formatters in the set provided.
|
||||
* JavaBean-friendly alternative to calling {@link #add(Formatter)}.
|
||||
* JavaBean-friendly alternative to calling {@link #addFormatterByType(Formatter)}.
|
||||
* @see #add(Formatter)
|
||||
*/
|
||||
public void setFormatters(Set<Formatter<?>> formatters) {
|
||||
for (Formatter<?> formatter : formatters) {
|
||||
add(formatter);
|
||||
addFormatterByType(formatter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the formatters in the map provided by type.
|
||||
* JavaBean-friendly alternative to calling {@link #add(Class, Formatter)}.
|
||||
* JavaBean-friendly alternative to calling {@link #addFormatterByType(Class, Formatter)}.
|
||||
* @see #add(Class, Formatter)
|
||||
*/
|
||||
public void setFormatterMap(Map<Class<?>, Formatter<?>> formatters) {
|
||||
for (Map.Entry<Class<?>, Formatter<?>> entry : formatters.entrySet()) {
|
||||
add(entry.getKey(), entry.getValue());
|
||||
addFormatterByType(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the formatters in the map provided by annotation type.
|
||||
* JavaBean-friendly alternative to calling {@link #addFormatterByAnnotation(Class, Formatter)}.
|
||||
* @see #add(Class, Formatter)
|
||||
*/
|
||||
public void setAnnotationFormatterMap(Map<Class<? extends Annotation>, Formatter<?>> formatters) {
|
||||
for (Map.Entry<Class<? extends Annotation>, Formatter<?>> entry : formatters.entrySet()) {
|
||||
addFormatterByAnnotation(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the annotation formatter factories in the set provided.
|
||||
* JavaBean-friendly alternative to calling {@link #add(AnnotationFormatterFactory)}.
|
||||
* JavaBean-friendly alternative to calling {@link #addFormatterByAnnotation(AnnotationFormatterFactory)}.
|
||||
* @see #add(AnnotationFormatterFactory)
|
||||
*/
|
||||
public void setAnnotationFormatterFactories(Set<AnnotationFormatterFactory> factories) {
|
||||
for (AnnotationFormatterFactory factory : factories) {
|
||||
add(factory);
|
||||
public void setAnnotationFormatterFactories(Set<AnnotationFormatterFactory<?, ?>> factories) {
|
||||
for (AnnotationFormatterFactory<?, ?> factory : factories) {
|
||||
addFormatterByAnnotation(factory);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the type conversion service that will be used to coerce objects to the
|
||||
* types required for formatting. Defaults to a {@link DefaultConversionService}.
|
||||
* @see #add(Class, Formatter)
|
||||
* @see #addFormatterByType(Class, Formatter)
|
||||
*/
|
||||
public void setConversionService(ConversionService conversionService) {
|
||||
Assert.notNull(conversionService, "ConversionService must not be null");
|
||||
@@ -117,12 +132,13 @@ public class GenericFormatterRegistry implements FormatterRegistry, BeanFactoryA
|
||||
/**
|
||||
* Take the context's default ConversionService if none specified locally.
|
||||
*/
|
||||
public void setBeanFactory(BeanFactory beanFactory) {
|
||||
public void setApplicationContext(ApplicationContext context) {
|
||||
if (this.conversionService == null &&
|
||||
beanFactory.containsBean(ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME)) {
|
||||
this.conversionService = beanFactory.getBean(
|
||||
context.containsBean(ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME)) {
|
||||
this.conversionService = context.getBean(
|
||||
ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME, ConversionService.class);
|
||||
}
|
||||
this.applicationContext = context;
|
||||
}
|
||||
|
||||
|
||||
@@ -157,6 +173,7 @@ public class GenericFormatterRegistry implements FormatterRegistry, BeanFactoryA
|
||||
clone.typeFormatters.putAll(this.typeFormatters);
|
||||
clone.annotationFormatters.putAll(this.annotationFormatters);
|
||||
clone.conversionService = this.conversionService;
|
||||
clone.applicationContext = applicationContext;
|
||||
clone.shared = false;
|
||||
return clone;
|
||||
}
|
||||
@@ -164,32 +181,48 @@ public class GenericFormatterRegistry implements FormatterRegistry, BeanFactoryA
|
||||
|
||||
// implementing FormatterRegistry
|
||||
|
||||
public <T> void add(Formatter<T> formatter) {
|
||||
this.typeFormatters.put(getFormattedObjectType(formatter.getClass()), formatter);
|
||||
}
|
||||
|
||||
public void add(Class<?> type, Formatter<?> formatter) {
|
||||
public void addFormatterByType(Class<?> type, Formatter<?> formatter) {
|
||||
Class<?> formattedObjectType = getFormattedObjectType(formatter.getClass());
|
||||
if (!this.conversionService.canConvert(formattedObjectType, type)) {
|
||||
throw new IllegalArgumentException("Unable to register formatter " + formatter + " for type [" + type.getName() + "]; not able to convert from [" + formattedObjectType.getName() + "] to parse");
|
||||
throw new IllegalArgumentException("Unable to register Formatter " + formatter + " for type [" + type.getName() + "]; not able to convert from [" + formattedObjectType.getName() + "] to parse");
|
||||
}
|
||||
if (!this.conversionService.canConvert(type, formattedObjectType)) {
|
||||
throw new IllegalArgumentException("Unable to register formatter " + formatter + " for type [" + type.getName() + "]; not able to convert to [" + formattedObjectType.getName() + "] to format");
|
||||
throw new IllegalArgumentException("Unable to register Formatter " + formatter + " for type [" + type.getName() + "]; not able to convert to [" + formattedObjectType.getName() + "] to format");
|
||||
}
|
||||
this.typeFormatters.put(type, formatter);
|
||||
}
|
||||
|
||||
public void add(AnnotationFormatterFactory<?, ?> factory) {
|
||||
public <T> void addFormatterByType(Formatter<T> formatter) {
|
||||
Class formattedObjectType = getFormattedObjectType(formatter.getClass());
|
||||
this.typeFormatters.put(formattedObjectType, formatter);
|
||||
}
|
||||
|
||||
public void addFormatterByAnnotation(Class<? extends Annotation> annotationType, Formatter<?> formatter) {
|
||||
this.annotationFormatters.put(annotationType, new SimpleAnnotationFormatterFactory(formatter));
|
||||
}
|
||||
|
||||
public <A extends Annotation, T> void addFormatterByAnnotation(AnnotationFormatterFactory<A, T> factory) {
|
||||
this.annotationFormatters.put(getAnnotationType(factory.getClass()), factory);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Formatter<T> getFormatter(Class<T> targetType) {
|
||||
return (Formatter<T>) getFormatter(TypeDescriptor.valueOf(targetType));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Formatter<Object> getFormatter(TypeDescriptor type) {
|
||||
Assert.notNull(type, "The TypeDescriptor is required");
|
||||
Assert.notNull(type, "TypeDescriptor is required");
|
||||
Formatter<Object> formatter = getAnnotationFormatter(type);
|
||||
if (formatter == null) {
|
||||
formatter = getTypeFormatter(type.getType());
|
||||
}
|
||||
if (formatter != null) {
|
||||
Class<?> formattedObjectType = getFormattedObjectType(formatter.getClass());
|
||||
if (!type.getType().isAssignableFrom(formattedObjectType)) {
|
||||
return new ConvertingFormatter(type.getType(), formattedObjectType, formatter);
|
||||
}
|
||||
}
|
||||
return formatter;
|
||||
}
|
||||
|
||||
@@ -250,37 +283,33 @@ public class GenericFormatterRegistry implements FormatterRegistry, BeanFactoryA
|
||||
classToIntrospect = classToIntrospect.getSuperclass();
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"Unable to extract Annotation type A argument from AnnotationFormatterFactory ["
|
||||
+ factoryClass.getName() + "]; does the factory parameterize the <A> generic type?");
|
||||
"Unable to extract Annotation type A argument from AnnotationFormatterFactory [" +
|
||||
factoryClass.getName() + "]; does the factory parameterize the <A> generic type?");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Formatter getAnnotationFormatter(TypeDescriptor type) {
|
||||
Annotation[] annotations = type.getAnnotations();
|
||||
for (Annotation a : annotations) {
|
||||
AnnotationFormatterFactory factory = annotationFormatters.get(a.annotationType());
|
||||
for (Annotation ann : annotations) {
|
||||
AnnotationFormatterFactory factory = this.annotationFormatters.get(ann.annotationType());
|
||||
if (factory != null) {
|
||||
return factory.getFormatter(a);
|
||||
return factory.getFormatter(ann);
|
||||
}
|
||||
else {
|
||||
Formatted formattedAnnotation = ann.annotationType().getAnnotation(Formatted.class);
|
||||
if (formattedAnnotation != null) {
|
||||
Formatter formatter = createFormatter(formattedAnnotation.value());
|
||||
this.annotationFormatters.put(ann.annotationType(), new SimpleAnnotationFormatterFactory(formatter));
|
||||
return formatter;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private Formatter getTypeFormatter(Class<?> type) {
|
||||
Assert.notNull(type, "The Class of the object to format is required");
|
||||
Formatter formatter = findFormatter(type);
|
||||
if (formatter != null) {
|
||||
Class<?> formattedObjectType = getFormattedObjectType(formatter.getClass());
|
||||
if (type.isAssignableFrom(formattedObjectType)) {
|
||||
return formatter;
|
||||
}
|
||||
else {
|
||||
return new ConvertingFormatter(type, formattedObjectType, formatter);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return getDefaultFormatter(type);
|
||||
}
|
||||
return (formatter != null ? formatter : getDefaultFormatter(type));
|
||||
}
|
||||
|
||||
private Formatter<?> findFormatter(Class<?> type) {
|
||||
@@ -288,7 +317,7 @@ public class GenericFormatterRegistry implements FormatterRegistry, BeanFactoryA
|
||||
classQueue.addFirst(type);
|
||||
while (!classQueue.isEmpty()) {
|
||||
Class currentClass = classQueue.removeLast();
|
||||
Formatter<?> formatter = typeFormatters.get(currentClass);
|
||||
Formatter<?> formatter = this.typeFormatters.get(currentClass);
|
||||
if (formatter != null) {
|
||||
return formatter;
|
||||
}
|
||||
@@ -306,32 +335,29 @@ public class GenericFormatterRegistry implements FormatterRegistry, BeanFactoryA
|
||||
private Formatter<?> getDefaultFormatter(Class<?> type) {
|
||||
Formatted formatted = AnnotationUtils.findAnnotation(type, Formatted.class);
|
||||
if (formatted != null) {
|
||||
Class formatterClass = formatted.value();
|
||||
try {
|
||||
Formatter formatter = (Formatter) formatterClass.newInstance();
|
||||
typeFormatters.put(type, formatter);
|
||||
return formatter;
|
||||
} catch (InstantiationException e) {
|
||||
throw new IllegalStateException(
|
||||
"Formatter referenced by @Formatted annotation does not have default constructor", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException(
|
||||
"Formatter referenced by @Formatted annotation does not have public constructor", e);
|
||||
}
|
||||
Formatter formatter = createFormatter(formatted.value());
|
||||
this.typeFormatters.put(type, formatter);
|
||||
return formatter;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Formatter<?> createFormatter(Class<? extends Formatter> formatterClass) {
|
||||
return (this.applicationContext != null ?
|
||||
this.applicationContext.getAutowireCapableBeanFactory().createBean(formatterClass) :
|
||||
BeanUtils.instantiate(formatterClass));
|
||||
}
|
||||
|
||||
|
||||
private class ConvertingFormatter implements Formatter {
|
||||
|
||||
private Class<?> type;
|
||||
private final Class<?> type;
|
||||
|
||||
private Class<?> formattedObjectType;
|
||||
private final Class<?> formattedObjectType;
|
||||
|
||||
private Formatter targetFormatter;
|
||||
private final Formatter targetFormatter;
|
||||
|
||||
public ConvertingFormatter(Class<?> type, Class<?> formattedObjectType, Formatter targetFormatter) {
|
||||
this.type = type;
|
||||
@@ -341,16 +367,29 @@ public class GenericFormatterRegistry implements FormatterRegistry, BeanFactoryA
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public String format(Object object, Locale locale) {
|
||||
object = conversionService.convert(object, formattedObjectType);
|
||||
return targetFormatter.format(object, locale);
|
||||
object = conversionService.convert(object, this.formattedObjectType);
|
||||
return this.targetFormatter.format(object, locale);
|
||||
}
|
||||
|
||||
public Object parse(String formatted, Locale locale) throws ParseException {
|
||||
Object parsed = targetFormatter.parse(formatted, locale);
|
||||
parsed = conversionService.convert(parsed, type);
|
||||
Object parsed = this.targetFormatter.parse(formatted, locale);
|
||||
parsed = conversionService.convert(parsed, this.type);
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class SimpleAnnotationFormatterFactory implements AnnotationFormatterFactory {
|
||||
|
||||
private final Formatter instance;
|
||||
|
||||
public SimpleAnnotationFormatterFactory(Formatter instance) {
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
public Formatter getFormatter(Annotation annotation) {
|
||||
return this.instance;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
|
||||
/**
|
||||
*
|
||||
* Generic support for UI layer concepts.
|
||||
* Provides a generic ModelMap for model holding.
|
||||
*
|
||||
*/
|
||||
package org.springframework.ui;
|
||||
|
||||
|
||||
@@ -106,11 +106,7 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul
|
||||
@Override
|
||||
protected Object formatFieldValue(String field, Object value) {
|
||||
String fixedField = fixedField(field);
|
||||
TypeDescriptor td = getPropertyAccessor().getPropertyTypeDescriptor(fixedField);
|
||||
Formatter<Object> formatter = (this.formatterRegistry != null ? this.formatterRegistry.getFormatter(td) : null);
|
||||
if (formatter != null) {
|
||||
return formatter.format(value, LocaleContextHolder.getLocale());
|
||||
}
|
||||
// Try custom editor...
|
||||
PropertyEditor customEditor = getCustomEditor(fixedField);
|
||||
if (customEditor != null) {
|
||||
customEditor.setValue(value);
|
||||
@@ -121,6 +117,13 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul
|
||||
return textValue;
|
||||
}
|
||||
}
|
||||
// Try custom formatter...
|
||||
TypeDescriptor td = getPropertyAccessor().getPropertyTypeDescriptor(fixedField);
|
||||
Formatter<Object> formatter = (this.formatterRegistry != null ? this.formatterRegistry.getFormatter(td) : null);
|
||||
if (formatter != null) {
|
||||
return formatter.format(value, LocaleContextHolder.getLocale());
|
||||
}
|
||||
// Nothing found: return value as-is.
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -144,14 +147,18 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul
|
||||
*/
|
||||
@Override
|
||||
public PropertyEditor findEditor(String field, Class valueType) {
|
||||
TypeDescriptor td = (valueType != null ? TypeDescriptor.valueOf(valueType) :
|
||||
getPropertyAccessor().getPropertyTypeDescriptor(fixedField(field)));
|
||||
final Formatter<Object> formatter =
|
||||
(this.formatterRegistry != null ? this.formatterRegistry.getFormatter(td) : null);
|
||||
if (formatter != null) {
|
||||
return new FormattingPropertyEditorAdapter(formatter);
|
||||
PropertyEditor editor = super.findEditor(field, valueType);
|
||||
if (editor == null) {
|
||||
TypeDescriptor td = (field != null ?
|
||||
getPropertyAccessor().getPropertyTypeDescriptor(fixedField(field)) :
|
||||
TypeDescriptor.valueOf(valueType));
|
||||
Formatter<Object> formatter =
|
||||
(this.formatterRegistry != null ? this.formatterRegistry.getFormatter(td) : null);
|
||||
if (formatter != null) {
|
||||
editor = new FormattingPropertyEditorAdapter(formatter);
|
||||
}
|
||||
}
|
||||
return super.findEditor(field, valueType);
|
||||
return editor;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -36,8 +36,8 @@ import org.springframework.beans.TypeConverter;
|
||||
import org.springframework.beans.TypeMismatchException;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.ui.format.FormatterRegistry;
|
||||
import org.springframework.ui.format.support.GenericFormatterRegistry;
|
||||
import org.springframework.ui.format.support.FormattingConversionServiceAdapter;
|
||||
import org.springframework.ui.format.support.GenericFormatterRegistry;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.PatternMatchUtils;
|
||||
@@ -135,6 +135,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||
|
||||
private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor();
|
||||
|
||||
private Validator validator;
|
||||
|
||||
private FormatterRegistry formatterRegistry;
|
||||
|
||||
|
||||
@@ -443,6 +445,23 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||
return this.bindingErrorProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Validator to apply after each binding step.
|
||||
*/
|
||||
public void setValidator(Validator validator) {
|
||||
if (validator != null && (getTarget() != null && !validator.supports(getTarget().getClass()))) {
|
||||
throw new IllegalStateException("Invalid target for Validator [" + validator + "]: " + getTarget());
|
||||
}
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Validator to apply after each binding step, if any.
|
||||
*/
|
||||
public Validator getValidator() {
|
||||
return this.validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the FormatterRegistry to use for obtaining Formatters in preference
|
||||
* to JavaBeans PropertyEditors.
|
||||
@@ -633,6 +652,18 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invoke the specified Validator, if any.
|
||||
* @see #setValidator(Validator)
|
||||
* @see #getBindingResult()
|
||||
*/
|
||||
public void validate() {
|
||||
Validator validator = getValidator();
|
||||
if (validator != null) {
|
||||
validator.validate(getTarget(), getBindingResult());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close this DataBinder, which may result in throwing
|
||||
* a BindException if it encountered any errors.
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.util.Iterator;
|
||||
import java.util.Set;
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import javax.validation.ValidatorFactory;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanInitializationException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
|
||||
/**
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
*/
|
||||
public class BeanValidationPostProcessor implements BeanPostProcessor, InitializingBean {
|
||||
|
||||
private javax.validation.Validator validator;
|
||||
|
||||
|
||||
public void setValidator(Validator validator) {
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
public void setValidatorFactory(ValidatorFactory validatorFactory) {
|
||||
this.validator = validatorFactory.getValidator();
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() {
|
||||
if (this.validator == null) {
|
||||
Validation.buildDefaultValidatorFactory().getValidator();
|
||||
}
|
||||
}
|
||||
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
return bean;
|
||||
}
|
||||
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
Set<ConstraintViolation<Object>> result = this.validator.validate(bean);
|
||||
if (!result.isEmpty()) {
|
||||
StringBuilder sb = new StringBuilder("Bean state is invalid: ");
|
||||
for (Iterator<ConstraintViolation<Object>> it = result.iterator(); it.hasNext();) {
|
||||
ConstraintViolation<Object> violation = it.next();
|
||||
sb.append(violation.getPropertyPath()).append(" - ").append(violation.getMessage());
|
||||
if (it.hasNext()) {
|
||||
sb.append("; ");
|
||||
}
|
||||
}
|
||||
throw new BeanInitializationException(sb.toString());
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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 javax.validation.MessageInterpolator;
|
||||
import javax.validation.TraversableResolver;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import javax.validation.ValidatorContext;
|
||||
import javax.validation.ValidatorFactory;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
/**
|
||||
* Configurable bean class that exposes a specific JSR-303 Validator
|
||||
* through its original interface as well as through the Spring
|
||||
* {@link org.springframework.validation.Validator} interface.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
*/
|
||||
public class CustomValidatorBean extends SpringValidatorAdapter implements Validator, InitializingBean {
|
||||
|
||||
private ValidatorFactory validatorFactory;
|
||||
|
||||
private MessageInterpolator messageInterpolator;
|
||||
|
||||
private TraversableResolver traversableResolver;
|
||||
|
||||
|
||||
/**
|
||||
* Set the ValidatorFactory to obtain the target Validator from.
|
||||
* <p>Default is {@link javax.validation.Validation#buildDefaultValidatorFactory()}.
|
||||
*/
|
||||
public void setValidatorFactory(ValidatorFactory validatorFactory) {
|
||||
this.validatorFactory = validatorFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a custom MessageInterpolator to use for this Validator.
|
||||
*/
|
||||
public void setMessageInterpolator(MessageInterpolator messageInterpolator) {
|
||||
this.messageInterpolator = messageInterpolator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a custom TraversableResolver to use for this Validator.
|
||||
*/
|
||||
public void setTraversableResolver(TraversableResolver traversableResolver) {
|
||||
this.traversableResolver = traversableResolver;
|
||||
}
|
||||
|
||||
|
||||
public void afterPropertiesSet() {
|
||||
if (this.validatorFactory == null) {
|
||||
this.validatorFactory = Validation.buildDefaultValidatorFactory();
|
||||
}
|
||||
|
||||
ValidatorContext validatorContext = this.validatorFactory.usingContext();
|
||||
validatorContext.messageInterpolator(new LocaleContextMessageInterpolator(
|
||||
this.messageInterpolator, this.validatorFactory.getMessageInterpolator()));
|
||||
if (this.traversableResolver != null) {
|
||||
validatorContext.traversableResolver(this.traversableResolver);
|
||||
}
|
||||
|
||||
setTargetValidator(validatorContext.getValidator());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* 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.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import javax.validation.Configuration;
|
||||
import javax.validation.ConstraintValidatorFactory;
|
||||
import javax.validation.MessageInterpolator;
|
||||
import javax.validation.TraversableResolver;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import javax.validation.ValidatorContext;
|
||||
import javax.validation.ValidatorFactory;
|
||||
import javax.validation.spi.ValidationProvider;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* This is the central class for <code>javax.validation</code> (JSR-303) setup
|
||||
* in a Spring application context: It bootstraps a <code>javax.validation.ValidationFactory</code>
|
||||
* and exposes it through the Spring {@link org.springframework.validation.Validator} interface
|
||||
* as well as through the JSR-303 {@link javax.validation.Validator} interface and the
|
||||
* {@link javax.validation.ValidatorFactory} interface itself.
|
||||
*
|
||||
* <p>When talking to an instance of this bean through the Spring or JSR-303 Validator interfaces,
|
||||
* you'll be talking to the default Validator of the underlying ValidatorFactory. This is very
|
||||
* convenient in that you don't have to perform yet another call on the factory, assuming that
|
||||
* you will almost always use the default Validator anyway. This can also be injected directly
|
||||
* into any target dependency of type {@link org.springframework.validation.Validator}!
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see javax.validation.ValidatorFactory
|
||||
* @see javax.validation.Validator
|
||||
* @see javax.validation.Validation#buildDefaultValidatorFactory()
|
||||
*/
|
||||
public class LocalValidatorFactoryBean extends SpringValidatorAdapter
|
||||
implements ValidatorFactory, Validator, ApplicationContextAware, InitializingBean {
|
||||
|
||||
private Class<? extends ValidationProvider> providerClass;
|
||||
|
||||
private MessageInterpolator messageInterpolator;
|
||||
|
||||
private TraversableResolver traversableResolver;
|
||||
|
||||
private ConstraintValidatorFactory constraintValidatorFactory;
|
||||
|
||||
private Resource[] mappingLocations;
|
||||
|
||||
private final Map<String, String> validationPropertyMap = new HashMap<String, String>();
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private ValidatorFactory validatorFactory;
|
||||
|
||||
|
||||
/**
|
||||
* Specify the desired provider class, if any.
|
||||
* <p>If not specified, JSR-303's default search mechanism will be used.
|
||||
* @see javax.validation.Validation#byProvider(Class)
|
||||
* @see javax.validation.Validation#byDefaultProvider()
|
||||
*/
|
||||
public void setProviderClass(Class<? extends ValidationProvider> providerClass) {
|
||||
this.providerClass = providerClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a custom MessageInterpolator to use for this ValidatorFactory
|
||||
* and its exposed default Validator.
|
||||
*/
|
||||
public void setMessageInterpolator(MessageInterpolator messageInterpolator) {
|
||||
this.messageInterpolator = messageInterpolator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a custom TraversableResolver to use for this ValidatorFactory
|
||||
* and its exposed default Validator.
|
||||
*/
|
||||
public void setTraversableResolver(TraversableResolver traversableResolver) {
|
||||
this.traversableResolver = traversableResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a custom ConstraintValidatorFactory to use for this ValidatorFactory.
|
||||
* <p>Default is a {@link SpringConstraintValidatorFactory}, delegating to the
|
||||
* containing ApplicationContext for creating autowired ConstraintValidator instances.
|
||||
*/
|
||||
public void setConstraintValidatorFactory(ConstraintValidatorFactory constraintValidatorFactory) {
|
||||
this.constraintValidatorFactory = constraintValidatorFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify resource locations to load XML constraint mapping files from, if any.
|
||||
*/
|
||||
public void setMappingLocations(Resource[] mappingLocations) {
|
||||
this.mappingLocations = mappingLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify bean validation properties to be passed to the validation provider.
|
||||
* <p>Can be populated with a String
|
||||
* "value" (parsed via PropertiesEditor) or a "props" element in XML bean definitions.
|
||||
* @see javax.validation.Configuration#addProperty(String, String)
|
||||
*/
|
||||
public void setValidationProperties(Properties jpaProperties) {
|
||||
CollectionUtils.mergePropertiesIntoMap(jpaProperties, this.validationPropertyMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify bean validation properties to be passed to the validation provider as a Map.
|
||||
* <p>Can be populated with a
|
||||
* "map" or "props" element in XML bean definitions.
|
||||
* @see javax.validation.Configuration#addProperty(String, String)
|
||||
*/
|
||||
public void setValidationPropertyMap(Map<String, String> validationProperties) {
|
||||
if (validationProperties != null) {
|
||||
this.validationPropertyMap.putAll(validationProperties);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow Map access to the bean validation properties to be passed to the validation provider,
|
||||
* with the option to add or override specific entries.
|
||||
* <p>Useful for specifying entries directly, for example via "validationPropertyMap[myKey]".
|
||||
*/
|
||||
public Map<String, String> getValidationPropertyMap() {
|
||||
return this.validationPropertyMap;
|
||||
}
|
||||
|
||||
public void setApplicationContext(ApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
|
||||
public void afterPropertiesSet() {
|
||||
Configuration configuration = (this.providerClass != null ?
|
||||
Validation.byProvider(this.providerClass).configure() :
|
||||
Validation.byDefaultProvider().configure());
|
||||
|
||||
configuration.messageInterpolator(new LocaleContextMessageInterpolator(
|
||||
this.messageInterpolator, configuration.getDefaultMessageInterpolator()));
|
||||
|
||||
if (this.traversableResolver != null) {
|
||||
configuration.traversableResolver(this.traversableResolver);
|
||||
}
|
||||
|
||||
ConstraintValidatorFactory targetConstraintValidatorFactory = this.constraintValidatorFactory;
|
||||
if (targetConstraintValidatorFactory == null && this.applicationContext != null) {
|
||||
targetConstraintValidatorFactory =
|
||||
new SpringConstraintValidatorFactory(this.applicationContext.getAutowireCapableBeanFactory());
|
||||
}
|
||||
if (targetConstraintValidatorFactory != null) {
|
||||
configuration.constraintValidatorFactory(targetConstraintValidatorFactory);
|
||||
}
|
||||
|
||||
if (this.mappingLocations != null) {
|
||||
for (Resource location : this.mappingLocations) {
|
||||
try {
|
||||
configuration.addMapping(location.getInputStream());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Cannot read mapping resource: " + location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> entry : this.validationPropertyMap.entrySet()) {
|
||||
configuration.addProperty(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
this.validatorFactory = configuration.buildValidatorFactory();
|
||||
setTargetValidator(this.validatorFactory.getValidator());
|
||||
}
|
||||
|
||||
|
||||
public Validator getValidator() {
|
||||
return this.validatorFactory.getValidator();
|
||||
}
|
||||
|
||||
public ValidatorContext usingContext() {
|
||||
return this.validatorFactory.usingContext();
|
||||
}
|
||||
|
||||
public MessageInterpolator getMessageInterpolator() {
|
||||
return this.validatorFactory.getMessageInterpolator();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.util.Locale;
|
||||
import javax.validation.MessageInterpolator;
|
||||
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Delegates to a target {@link MessageInterpolator} implementation but enforces Spring's
|
||||
* managed Locale. Typically used to wrap the validation provider's default interpolator.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see org.springframework.context.i18n.LocaleContextHolder#getLocale()
|
||||
*/
|
||||
public class LocaleContextMessageInterpolator implements MessageInterpolator {
|
||||
|
||||
private final MessageInterpolator targetInterpolator;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new LocaleContextMessageInterpolator, wrapping the given target interpolator.
|
||||
* @param targetInterpolator the target MessageInterpolator to wrap
|
||||
*/
|
||||
public LocaleContextMessageInterpolator(MessageInterpolator targetInterpolator) {
|
||||
Assert.notNull(targetInterpolator, "Target MessageInterpolator must not be null");
|
||||
this.targetInterpolator = targetInterpolator;
|
||||
}
|
||||
|
||||
LocaleContextMessageInterpolator(MessageInterpolator customInterpolator, MessageInterpolator defaultInterpolator) {
|
||||
this.targetInterpolator = (customInterpolator != null ? customInterpolator : defaultInterpolator);
|
||||
}
|
||||
|
||||
|
||||
public String interpolate(String message, Context context) {
|
||||
return this.targetInterpolator.interpolate(message, context, LocaleContextHolder.getLocale());
|
||||
}
|
||||
|
||||
public String interpolate(String message, Context context, Locale locale) {
|
||||
return this.targetInterpolator.interpolate(message, context, locale);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorFactory;
|
||||
|
||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* JSR-303 {@link ConstraintValidatorFactory} implementation that delegates to a
|
||||
* Spring BeanFactory for creating autowired {@link ConstraintValidator} instances.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#createBean(Class)
|
||||
* @see org.springframework.context.ApplicationContext#getAutowireCapableBeanFactory()
|
||||
*/
|
||||
public class SpringConstraintValidatorFactory implements ConstraintValidatorFactory {
|
||||
|
||||
private final AutowireCapableBeanFactory beanFactory;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new SpringConstraintValidatorFactory for the given BeanFactory.
|
||||
* @param beanFactory the target BeanFactory
|
||||
*/
|
||||
public SpringConstraintValidatorFactory(AutowireCapableBeanFactory beanFactory) {
|
||||
Assert.notNull(beanFactory, "BeanFactory must not be null");
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
|
||||
public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
|
||||
return this.beanFactory.createBean(key);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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.util.Set;
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.metadata.BeanDescriptor;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
|
||||
/**
|
||||
* Adapter that takes a JSR-303 <code>javax.validator.Validator</code>
|
||||
* and exposes it as a Spring {@link org.springframework.validation.Validator}
|
||||
* while also exposing the original JSR-303 Validator interface itself.
|
||||
*
|
||||
* <p>Can be used as a programmatic wrapper. Also serves as base class for
|
||||
* {@link CustomValidatorBean} and {@link LocalValidatorFactoryBean}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
*/
|
||||
public class SpringValidatorAdapter implements Validator, javax.validation.Validator {
|
||||
|
||||
private javax.validation.Validator targetValidator;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new SpringValidatorAdapter for the given JSR-303 Validator.
|
||||
* @param targetValidator the JSR-303 Validator to wrap
|
||||
*/
|
||||
public SpringValidatorAdapter(javax.validation.Validator targetValidator) {
|
||||
Assert.notNull(targetValidator, "Target Validator must not be null");
|
||||
this.targetValidator = targetValidator;
|
||||
}
|
||||
|
||||
SpringValidatorAdapter() {
|
||||
}
|
||||
|
||||
void setTargetValidator(javax.validation.Validator targetValidator) {
|
||||
this.targetValidator = targetValidator;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Implementation of Spring Validator interface
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void validate(Object target, Errors errors) {
|
||||
Set<ConstraintViolation<Object>> result = this.targetValidator.validate(target);
|
||||
for (ConstraintViolation<Object> violation : result) {
|
||||
errors.rejectValue(violation.getPropertyPath().toString(),
|
||||
violation.getConstraintDescriptor().getAnnotation().annotationType().getSimpleName(),
|
||||
violation.getConstraintDescriptor().getAttributes().values().toArray(),
|
||||
violation.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Implementation of JSR-303 Validator interface
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
public <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) {
|
||||
return this.targetValidator.validate(object, groups);
|
||||
}
|
||||
|
||||
public <T> Set<ConstraintViolation<T>> validateProperty(T object, String propertyName, Class<?>... groups) {
|
||||
return this.targetValidator.validateProperty(object, propertyName, groups);
|
||||
}
|
||||
|
||||
public <T> Set<ConstraintViolation<T>> validateValue(
|
||||
Class<T> beanType, String propertyName, Object value, Class<?>... groups) {
|
||||
|
||||
return this.targetValidator.validateValue(beanType, propertyName, groups);
|
||||
}
|
||||
|
||||
public BeanDescriptor getConstraintsForClass(Class<?> clazz) {
|
||||
return this.targetValidator.getConstraintsForClass(clazz);
|
||||
}
|
||||
|
||||
public <T> T unwrap(Class<T> type) {
|
||||
return this.targetValidator.unwrap(type);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Support classes for integrating a JSR-303 Bean Validation provider
|
||||
* (such as Hibernate Validator 4.0) into a Spring ApplicationContext
|
||||
* and in particular with Spring's data binding and validation APIs.
|
||||
*
|
||||
* <p>The central class is {@link LocalValidatorFactoryBean} which
|
||||
* defines a shared ValidatorFactory/Validator setup for availability
|
||||
* to other Spring components.
|
||||
*/
|
||||
package org.springframework.validation.beanvalidation;
|
||||
@@ -1,9 +1,5 @@
|
||||
|
||||
/**
|
||||
*
|
||||
* Provides data binding and validation functionality,
|
||||
* for usage in business and/or UI layers.
|
||||
*
|
||||
*/
|
||||
package org.springframework.validation;
|
||||
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
|
||||
/**
|
||||
*
|
||||
* Support classes for handling validation results.
|
||||
*
|
||||
*/
|
||||
package org.springframework.validation.support;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user