Browse Source
git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@1335 50f2f4bb-b051-0410-bef5-90022cba6387pull/1/head
5 changed files with 566 additions and 500 deletions
@ -0,0 +1,518 @@ |
|||||||
|
/* |
||||||
|
* 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.binding.support; |
||||||
|
|
||||||
|
import java.lang.annotation.Annotation; |
||||||
|
import java.lang.reflect.Array; |
||||||
|
import java.lang.reflect.ParameterizedType; |
||||||
|
import java.lang.reflect.Type; |
||||||
|
import java.lang.reflect.TypeVariable; |
||||||
|
import java.text.ParseException; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collection; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Locale; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import org.springframework.context.expression.MapAccessor; |
||||||
|
import org.springframework.context.i18n.LocaleContextHolder; |
||||||
|
import org.springframework.core.GenericTypeResolver; |
||||||
|
import org.springframework.core.convert.TypeConverter; |
||||||
|
import org.springframework.core.convert.TypeDescriptor; |
||||||
|
import org.springframework.core.convert.support.DefaultTypeConverter; |
||||||
|
import org.springframework.expression.EvaluationContext; |
||||||
|
import org.springframework.expression.EvaluationException; |
||||||
|
import org.springframework.expression.Expression; |
||||||
|
import org.springframework.expression.ExpressionException; |
||||||
|
import org.springframework.expression.ExpressionParser; |
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParser; |
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParserConfiguration; |
||||||
|
import org.springframework.expression.spel.support.StandardEvaluationContext; |
||||||
|
import org.springframework.expression.spel.support.StandardTypeConverter; |
||||||
|
import org.springframework.ui.binding.Binder; |
||||||
|
import org.springframework.ui.binding.Binding; |
||||||
|
import org.springframework.ui.binding.BindingConfiguration; |
||||||
|
import org.springframework.ui.binding.BindingResult; |
||||||
|
import org.springframework.ui.binding.UserValue; |
||||||
|
import org.springframework.ui.format.AnnotationFormatterFactory; |
||||||
|
import org.springframework.ui.format.Formatter; |
||||||
|
|
||||||
|
/** |
||||||
|
* Binds user-entered values to properties of a model object. |
||||||
|
* @author Keith Donald |
||||||
|
* |
||||||
|
* @param <T> The type of model object this binder binds to |
||||||
|
* @see #add(BindingConfiguration) |
||||||
|
* @see #bind(Map) |
||||||
|
*/ |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public class GenericBinder<M> implements Binder<M> { |
||||||
|
|
||||||
|
private static final String[] EMPTY_STRING_ARRAY = new String[0]; |
||||||
|
|
||||||
|
private M model; |
||||||
|
|
||||||
|
private Map<String, Binding> bindings; |
||||||
|
|
||||||
|
private Map<Class, Formatter> typeFormatters = new HashMap<Class, Formatter>(); |
||||||
|
|
||||||
|
private Map<Class, AnnotationFormatterFactory> annotationFormatters = new HashMap<Class, AnnotationFormatterFactory>(); |
||||||
|
|
||||||
|
private ExpressionParser expressionParser; |
||||||
|
|
||||||
|
private TypeConverter typeConverter; |
||||||
|
|
||||||
|
private boolean strict = false; |
||||||
|
|
||||||
|
private static Formatter defaultFormatter = new Formatter() { |
||||||
|
public String format(Object object, Locale locale) { |
||||||
|
if (object == null) { |
||||||
|
return ""; |
||||||
|
} else { |
||||||
|
return object.toString(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public Object parse(String formatted, Locale locale) throws ParseException { |
||||||
|
if (formatted == "") { |
||||||
|
return null; |
||||||
|
} else { |
||||||
|
return formatted; |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new binder for the model object. |
||||||
|
* @param model the model object containing properties this binder will bind to |
||||||
|
*/ |
||||||
|
public GenericBinder(M model) { |
||||||
|
this.model = model; |
||||||
|
bindings = new HashMap<String, Binding>(); |
||||||
|
int parserConfig = SpelExpressionParserConfiguration.CreateListsOnAttemptToIndexIntoNull |
||||||
|
| SpelExpressionParserConfiguration.GrowListsOnIndexBeyondSize; |
||||||
|
expressionParser = new SpelExpressionParser(parserConfig); |
||||||
|
typeConverter = new DefaultTypeConverter(); |
||||||
|
} |
||||||
|
|
||||||
|
public void setStrict(boolean strict) { |
||||||
|
this.strict = strict; |
||||||
|
} |
||||||
|
|
||||||
|
public Binding add(BindingConfiguration binding) { |
||||||
|
Binding newBinding; |
||||||
|
try { |
||||||
|
newBinding = new BindingImpl(binding); |
||||||
|
} catch (org.springframework.expression.ParseException e) { |
||||||
|
throw new IllegalArgumentException(e); |
||||||
|
} |
||||||
|
bindings.put(binding.getProperty(), newBinding); |
||||||
|
return newBinding; |
||||||
|
} |
||||||
|
|
||||||
|
public void add(Formatter<?> formatter, Class<?> propertyType) { |
||||||
|
if (propertyType.isAnnotation()) { |
||||||
|
annotationFormatters.put(propertyType, new SimpleAnnotationFormatterFactory(formatter)); |
||||||
|
} else { |
||||||
|
typeFormatters.put(propertyType, formatter); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void add(AnnotationFormatterFactory<?, ?> factory) { |
||||||
|
annotationFormatters.put(getAnnotationType(factory), factory); |
||||||
|
} |
||||||
|
|
||||||
|
public M getModel() { |
||||||
|
return model; |
||||||
|
} |
||||||
|
|
||||||
|
public Binding getBinding(String property) { |
||||||
|
Binding binding = bindings.get(property); |
||||||
|
if (binding == null && !strict) { |
||||||
|
return add(new BindingConfiguration(property, null)); |
||||||
|
} else { |
||||||
|
return binding; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public List<BindingResult> bind(List<UserValue> userValues) { |
||||||
|
List<BindingResult> results = new ArrayList<BindingResult>(userValues.size()); |
||||||
|
for (UserValue value : userValues) { |
||||||
|
BindingImpl binding = (BindingImpl) getBinding(value.getProperty()); |
||||||
|
results.add(binding.setValue(value.getValue())); |
||||||
|
} |
||||||
|
return results; |
||||||
|
} |
||||||
|
|
||||||
|
class BindingImpl implements Binding { |
||||||
|
|
||||||
|
private Expression property; |
||||||
|
|
||||||
|
private Formatter formatter; |
||||||
|
|
||||||
|
public BindingImpl(BindingConfiguration config) throws org.springframework.expression.ParseException { |
||||||
|
property = expressionParser.parseExpression(config.getProperty()); |
||||||
|
formatter = config.getFormatter(); |
||||||
|
} |
||||||
|
|
||||||
|
// implementing Binding
|
||||||
|
|
||||||
|
public String getValue() { |
||||||
|
Object value; |
||||||
|
try { |
||||||
|
value = property.getValue(createEvaluationContext()); |
||||||
|
} catch (ExpressionException e) { |
||||||
|
throw new IllegalStateException("Failed to get property expression value - this should not happen", e); |
||||||
|
} |
||||||
|
return format(value); |
||||||
|
} |
||||||
|
|
||||||
|
public BindingResult setValue(Object value) { |
||||||
|
if (value instanceof String) { |
||||||
|
return setStringValue((String) value); |
||||||
|
} else if (value instanceof String[]) { |
||||||
|
return setStringValues((String[]) value); |
||||||
|
} else { |
||||||
|
return setObjectValue(value); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public String format(Object selectableValue) { |
||||||
|
Formatter formatter; |
||||||
|
try { |
||||||
|
formatter = getFormatter(); |
||||||
|
} catch (EvaluationException e) { |
||||||
|
throw new IllegalStateException("Failed to get property expression value type - this should not happen", e); |
||||||
|
} |
||||||
|
Class<?> formattedType = getFormattedObjectType(formatter); |
||||||
|
selectableValue = typeConverter.convert(selectableValue, formattedType); |
||||||
|
return formatter.format(selectableValue, LocaleContextHolder.getLocale()); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isCollection() { |
||||||
|
Class type; |
||||||
|
try { |
||||||
|
type = getValueType(); |
||||||
|
} catch (EvaluationException e) { |
||||||
|
throw new IllegalArgumentException("Failed to get property expression value type - this should not happen", e); |
||||||
|
} |
||||||
|
TypeDescriptor<?> typeDesc = TypeDescriptor.valueOf(type); |
||||||
|
return typeDesc.isCollection() || typeDesc.isArray(); |
||||||
|
} |
||||||
|
|
||||||
|
public String[] getCollectionValues() { |
||||||
|
Object multiValue; |
||||||
|
try { |
||||||
|
multiValue = property.getValue(createEvaluationContext()); |
||||||
|
} catch (EvaluationException e) { |
||||||
|
throw new IllegalStateException("Failed to get property expression value - this should not happen", e); |
||||||
|
} |
||||||
|
if (multiValue == null) { |
||||||
|
return EMPTY_STRING_ARRAY; |
||||||
|
} |
||||||
|
TypeDescriptor<?> type = TypeDescriptor.valueOf(multiValue.getClass()); |
||||||
|
String[] formattedValues; |
||||||
|
if (type.isCollection()) { |
||||||
|
Collection<?> values = ((Collection<?>) multiValue); |
||||||
|
formattedValues = (String[]) Array.newInstance(String.class, values.size()); |
||||||
|
copy(values, formattedValues); |
||||||
|
} else if (type.isArray()) { |
||||||
|
formattedValues = (String[]) Array.newInstance(String.class, Array.getLength(multiValue)); |
||||||
|
copy((Iterable<?>) multiValue, formattedValues); |
||||||
|
} else { |
||||||
|
throw new IllegalStateException(); |
||||||
|
} |
||||||
|
return formattedValues; |
||||||
|
} |
||||||
|
|
||||||
|
// internal helpers
|
||||||
|
|
||||||
|
private BindingResult setStringValue(String formatted) { |
||||||
|
Formatter formatter; |
||||||
|
try { |
||||||
|
formatter = getFormatter(); |
||||||
|
} catch (EvaluationException e) { |
||||||
|
// could occur the property was not found or is not readable
|
||||||
|
// TODO probably should not handle all EL failures, only type conversion & property not found?
|
||||||
|
return new ExpressionEvaluationErrorResult(property.getExpressionString(), formatted, e); |
||||||
|
} |
||||||
|
Object parsed; |
||||||
|
try { |
||||||
|
parsed = formatter.parse(formatted, LocaleContextHolder.getLocale()); |
||||||
|
} catch (ParseException e) { |
||||||
|
return new InvalidFormatResult(property.getExpressionString(), formatted, e); |
||||||
|
} |
||||||
|
return setValue(parsed, formatted); |
||||||
|
} |
||||||
|
|
||||||
|
private BindingResult setStringValues(String[] formatted) { |
||||||
|
Formatter formatter; |
||||||
|
try { |
||||||
|
formatter = getFormatter(); |
||||||
|
} catch (EvaluationException e) { |
||||||
|
// could occur the property was not found or is not readable
|
||||||
|
// TODO probably should not handle all EL failures, only type conversion & property not found?
|
||||||
|
return new ExpressionEvaluationErrorResult(property.getExpressionString(), formatted, e); |
||||||
|
} |
||||||
|
Class parsedType = getFormattedObjectType(formatter); |
||||||
|
if (parsedType == null) { |
||||||
|
parsedType = String.class; |
||||||
|
} |
||||||
|
Object parsed = Array.newInstance(parsedType, formatted.length); |
||||||
|
for (int i = 0; i < formatted.length; i++) { |
||||||
|
Object parsedValue; |
||||||
|
try { |
||||||
|
parsedValue = formatter.parse(formatted[i], LocaleContextHolder.getLocale()); |
||||||
|
} catch (ParseException e) { |
||||||
|
return new InvalidFormatResult(property.getExpressionString(), formatted, e); |
||||||
|
} |
||||||
|
Array.set(parsed, i, parsedValue); |
||||||
|
} |
||||||
|
return setValue(parsed, formatted); |
||||||
|
} |
||||||
|
|
||||||
|
private BindingResult setObjectValue(Object value) { |
||||||
|
return setValue(value, value); |
||||||
|
} |
||||||
|
|
||||||
|
private Formatter getFormatter() throws EvaluationException { |
||||||
|
if (formatter != null) { |
||||||
|
return formatter; |
||||||
|
} else { |
||||||
|
Class<?> type = getValueType(); |
||||||
|
Formatter<?> formatter = typeFormatters.get(type); |
||||||
|
if (formatter != null) { |
||||||
|
return formatter; |
||||||
|
} else { |
||||||
|
Annotation[] annotations = getAnnotations(); |
||||||
|
for (Annotation a : annotations) { |
||||||
|
AnnotationFormatterFactory factory = annotationFormatters.get(a.annotationType()); |
||||||
|
if (factory != null) { |
||||||
|
return factory.getFormatter(a); |
||||||
|
} |
||||||
|
} |
||||||
|
return defaultFormatter; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private Class<?> getValueType() throws EvaluationException { |
||||||
|
return property.getValueType(createEvaluationContext()); |
||||||
|
} |
||||||
|
|
||||||
|
private Annotation[] getAnnotations() throws EvaluationException { |
||||||
|
return property.getValueTypeDescriptor(createEvaluationContext()).getAnnotations(); |
||||||
|
} |
||||||
|
|
||||||
|
private void copy(Iterable<?> values, String[] formattedValues) { |
||||||
|
int i = 0; |
||||||
|
for (Object value : values) { |
||||||
|
formattedValues[i] = format(value); |
||||||
|
i++; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private BindingResult setValue(Object parsedValue, Object userValue) { |
||||||
|
try { |
||||||
|
property.setValue(createEvaluationContext(), parsedValue); |
||||||
|
return new SuccessResult(property.getExpressionString(), userValue); |
||||||
|
} catch (EvaluationException e) { |
||||||
|
return new ExpressionEvaluationErrorResult(property.getExpressionString(), userValue, e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private EvaluationContext createEvaluationContext() { |
||||||
|
StandardEvaluationContext context = new StandardEvaluationContext(); |
||||||
|
context.setRootObject(model); |
||||||
|
context.addPropertyAccessor(new MapAccessor()); |
||||||
|
context.setTypeConverter(new StandardTypeConverter(typeConverter)); |
||||||
|
return context; |
||||||
|
} |
||||||
|
|
||||||
|
private Class getAnnotationType(AnnotationFormatterFactory factory) { |
||||||
|
Class classToIntrospect = factory.getClass(); |
||||||
|
while (classToIntrospect != null) { |
||||||
|
Type[] genericInterfaces = classToIntrospect.getGenericInterfaces(); |
||||||
|
for (Type genericInterface : genericInterfaces) { |
||||||
|
if (genericInterface instanceof ParameterizedType) { |
||||||
|
ParameterizedType pInterface = (ParameterizedType) genericInterface; |
||||||
|
if (AnnotationFormatterFactory.class.isAssignableFrom((Class) pInterface.getRawType())) { |
||||||
|
return getParameterClass(pInterface.getActualTypeArguments()[0], factory.getClass()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
classToIntrospect = classToIntrospect.getSuperclass(); |
||||||
|
} |
||||||
|
throw new IllegalArgumentException( |
||||||
|
"Unable to extract Annotation type A argument from AnnotationFormatterFactory [" |
||||||
|
+ factory.getClass().getName() + "]; does the factory parameterize the <A> generic type?"); |
||||||
|
} |
||||||
|
|
||||||
|
private Class getFormattedObjectType(Formatter formatter) { |
||||||
|
// TODO consider caching this info
|
||||||
|
Class classToIntrospect = formatter.getClass(); |
||||||
|
while (classToIntrospect != null) { |
||||||
|
Type[] genericInterfaces = classToIntrospect.getGenericInterfaces(); |
||||||
|
for (Type genericInterface : genericInterfaces) { |
||||||
|
if (genericInterface instanceof ParameterizedType) { |
||||||
|
ParameterizedType pInterface = (ParameterizedType) genericInterface; |
||||||
|
if (Formatter.class.isAssignableFrom((Class) pInterface.getRawType())) { |
||||||
|
return getParameterClass(pInterface.getActualTypeArguments()[0], formatter.getClass()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
classToIntrospect = classToIntrospect.getSuperclass(); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
private Class getParameterClass(Type parameterType, Class converterClass) { |
||||||
|
if (parameterType instanceof TypeVariable) { |
||||||
|
parameterType = GenericTypeResolver.resolveTypeVariable((TypeVariable) parameterType, converterClass); |
||||||
|
} |
||||||
|
if (parameterType instanceof Class) { |
||||||
|
return (Class) parameterType; |
||||||
|
} |
||||||
|
throw new IllegalArgumentException("Unable to obtain the java.lang.Class for parameterType [" + parameterType |
||||||
|
+ "] on Formatter [" + converterClass.getName() + "]"); |
||||||
|
} |
||||||
|
|
||||||
|
static class SimpleAnnotationFormatterFactory implements AnnotationFormatterFactory { |
||||||
|
|
||||||
|
private Formatter formatter; |
||||||
|
|
||||||
|
public SimpleAnnotationFormatterFactory(Formatter formatter) { |
||||||
|
this.formatter = formatter; |
||||||
|
} |
||||||
|
|
||||||
|
public Formatter getFormatter(Annotation annotation) { |
||||||
|
return formatter; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
static class InvalidFormatResult implements BindingResult { |
||||||
|
|
||||||
|
private String property; |
||||||
|
|
||||||
|
private Object formatted; |
||||||
|
|
||||||
|
private ParseException e; |
||||||
|
|
||||||
|
public InvalidFormatResult(String property, Object formatted, ParseException e) { |
||||||
|
this.property = property; |
||||||
|
this.formatted = formatted; |
||||||
|
this.e = e; |
||||||
|
} |
||||||
|
|
||||||
|
public String getProperty() { |
||||||
|
return property; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isError() { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
public String getErrorCode() { |
||||||
|
return "invalidFormat"; |
||||||
|
} |
||||||
|
|
||||||
|
public Throwable getErrorCause() { |
||||||
|
return e; |
||||||
|
} |
||||||
|
|
||||||
|
public Object getUserValue() { |
||||||
|
return formatted; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static class ExpressionEvaluationErrorResult implements BindingResult { |
||||||
|
|
||||||
|
private String property; |
||||||
|
|
||||||
|
private Object formatted; |
||||||
|
|
||||||
|
private EvaluationException e; |
||||||
|
|
||||||
|
public ExpressionEvaluationErrorResult(String property, Object formatted, EvaluationException e) { |
||||||
|
this.property = property; |
||||||
|
this.formatted = formatted; |
||||||
|
this.e = e; |
||||||
|
} |
||||||
|
|
||||||
|
public String getProperty() { |
||||||
|
return property; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isError() { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
public String getErrorCode() { |
||||||
|
if (e.getMessage().startsWith("EL1034E")) { |
||||||
|
return "typeConversionFailure"; |
||||||
|
} else if (e.getMessage().startsWith("EL1008E")) { |
||||||
|
return "propertyNotFound"; |
||||||
|
} else { |
||||||
|
// TODO return more specific code based on underlying EvaluationException error code
|
||||||
|
return "couldNotSetValue"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public Throwable getErrorCause() { |
||||||
|
return e; |
||||||
|
} |
||||||
|
|
||||||
|
public Object getUserValue() { |
||||||
|
return formatted; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static class SuccessResult implements BindingResult { |
||||||
|
|
||||||
|
private String property; |
||||||
|
|
||||||
|
private Object formatted; |
||||||
|
|
||||||
|
public SuccessResult(String property, Object formatted) { |
||||||
|
this.property = property; |
||||||
|
this.formatted = formatted; |
||||||
|
} |
||||||
|
|
||||||
|
public String getProperty() { |
||||||
|
return property; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isError() { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
public String getErrorCode() { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
public Throwable getErrorCause() { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
public Object getUserValue() { |
||||||
|
return formatted; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
<html> |
||||||
|
<body> |
||||||
|
<p> |
||||||
|
Default implementation of the Binding API usable in any environment. |
||||||
|
</p> |
||||||
|
</body> |
||||||
|
</html> |
||||||
Loading…
Reference in new issue