diff --git a/spring-binding/pom.xml b/spring-binding/pom.xml index b0f8927a..e03739dd 100644 --- a/spring-binding/pom.xml +++ b/spring-binding/pom.xml @@ -44,6 +44,12 @@ 1.0 provided + + joda-time + joda-time + 1.6 + true + diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/ConversionService.java b/spring-binding/src/main/java/org/springframework/binding/convert/ConversionService.java index d8679afd..1744ce61 100644 --- a/spring-binding/src/main/java/org/springframework/binding/convert/ConversionService.java +++ b/spring-binding/src/main/java/org/springframework/binding/convert/ConversionService.java @@ -15,7 +15,6 @@ */ package org.springframework.binding.convert; -import java.util.Set; /** * A service interface for retrieving type conversion executors. The returned command objects are thread-safe and may be @@ -74,15 +73,6 @@ public interface ConversionService { public ConversionExecutor getConversionExecutor(String id, Class sourceClass, Class targetClass) throws ConversionExecutorNotFoundException; - /** - * Return all conversion executors capable of converting from the provided sourceClass. For - * example, getConversionExecutor(String.class) would return all converters that convert from String to - * some other Object. Mainly useful for adapting a set of converters to some other environment. - * @param sourceClass the source class converting from - * @return the conversion executors that can convert from that source class - */ - public Set getConversionExecutors(Class sourceClass); - /** * Lookup a class by its well-known alias. For example, long for java.lang.Long * @param alias the class alias @@ -90,4 +80,11 @@ public interface ConversionService { */ public Class getClassForAlias(String alias); + /** + * Return the underlying Spring ConversionService. + * + * @return the conversion service + */ + public org.springframework.core.convert.ConversionService getDelegateConversionService(); + } \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/converters/SpringConvertingConverterAdapter.java b/spring-binding/src/main/java/org/springframework/binding/convert/converters/SpringConvertingConverterAdapter.java new file mode 100644 index 00000000..51663417 --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/convert/converters/SpringConvertingConverterAdapter.java @@ -0,0 +1,64 @@ +/* + * Copyright 2004-2010 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.binding.convert.converters; + +import org.springframework.core.convert.ConversionService; +import org.springframework.util.Assert; + +/** + * A Spring Binding Converter that delegates to a Spring {@link ConversionService} to do the actual type conversion. + * + * @author Rossen Stoyanchev + */ +public class SpringConvertingConverterAdapter implements Converter { + + /** + * The source value type to convert from. + */ + private final Class sourceClass; + + /** + * The target value type to convert to. + */ + private final Class targetClass; + + /** + * The ConversionService that will perform the conversion. + */ + private ConversionService conversionService; + + public SpringConvertingConverterAdapter(Class sourceClass, Class targetClass, ConversionService conversionService) { + Assert.notNull(sourceClass, "The source class to convert from is required."); + Assert.notNull(targetClass, "The target class to convert to is required."); + Assert.notNull(conversionService, "A Spring ConversionService is required."); + this.sourceClass = sourceClass; + this.targetClass = targetClass; + this.conversionService = conversionService; + } + + public Object convertSourceToTargetClass(Object source, Class targetClass) throws Exception { + return conversionService.convert(source, targetClass); + } + + public Class getSourceClass() { + return sourceClass; + } + + public Class getTargetClass() { + return targetClass; + } + +} diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/service/DefaultConversionService.java b/spring-binding/src/main/java/org/springframework/binding/convert/service/DefaultConversionService.java index 04810f18..91e0e944 100644 --- a/spring-binding/src/main/java/org/springframework/binding/convert/service/DefaultConversionService.java +++ b/spring-binding/src/main/java/org/springframework/binding/convert/service/DefaultConversionService.java @@ -20,25 +20,8 @@ import java.math.BigInteger; import java.util.Date; import java.util.Locale; -import org.springframework.binding.convert.converters.CollectionToCollection; -import org.springframework.binding.convert.converters.NumberToNumber; -import org.springframework.binding.convert.converters.ObjectToCollection; -import org.springframework.binding.convert.converters.StringToBigDecimal; -import org.springframework.binding.convert.converters.StringToBigInteger; -import org.springframework.binding.convert.converters.StringToBoolean; -import org.springframework.binding.convert.converters.StringToByte; -import org.springframework.binding.convert.converters.StringToCharacter; -import org.springframework.binding.convert.converters.StringToDate; -import org.springframework.binding.convert.converters.StringToDouble; -import org.springframework.binding.convert.converters.StringToEnum; -import org.springframework.binding.convert.converters.StringToFloat; -import org.springframework.binding.convert.converters.StringToInteger; -import org.springframework.binding.convert.converters.StringToLabeledEnum; -import org.springframework.binding.convert.converters.StringToLocale; -import org.springframework.binding.convert.converters.StringToLong; -import org.springframework.binding.convert.converters.StringToShort; +import org.springframework.core.convert.ConversionService; import org.springframework.core.enums.LabeledEnum; -import org.springframework.util.ClassUtils; /** * Default, local implementation of a conversion service. Will automatically register from string converters for @@ -56,29 +39,26 @@ public class DefaultConversionService extends GenericConversionService { addDefaultAliases(); } + /** + * Creates a new default conversion service with an instance of a Spring ConversionService. + * + * @param delegateConversionService the Spring conversion service + */ + public DefaultConversionService(ConversionService delegateConversionService) { + super(delegateConversionService); + addDefaultConverters(); + addDefaultAliases(); + } + /** * Add all default converters to the conversion service. + * + * Note: Staring with Spring Web Flow 2.1, this method does not register any Spring Binding converters. All type + * conversion is driven through Spring's type conversion instead. + * + * @see GenericConversionService */ protected void addDefaultConverters() { - addConverter(new StringToByte()); - addConverter(new StringToBoolean()); - addConverter(new StringToCharacter()); - addConverter(new StringToShort()); - addConverter(new StringToInteger()); - addConverter(new StringToLong()); - addConverter(new StringToFloat()); - addConverter(new StringToDouble()); - addConverter(new StringToBigInteger()); - addConverter(new StringToBigDecimal()); - addConverter(new StringToLocale()); - addConverter(new StringToDate()); - addConverter(new StringToLabeledEnum()); - addConverter(new NumberToNumber()); - addConverter(new ObjectToCollection(this)); - addConverter(new CollectionToCollection(this)); - if (ClassUtils.isPresent("java.lang.Enum", this.getClass().getClassLoader())) { - addConverter(new StringToEnum()); - } } protected void addDefaultAliases() { diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/service/GenericConversionService.java b/spring-binding/src/main/java/org/springframework/binding/convert/service/GenericConversionService.java index 5093a0e1..9221b054 100644 --- a/spring-binding/src/main/java/org/springframework/binding/convert/service/GenericConversionService.java +++ b/spring-binding/src/main/java/org/springframework/binding/convert/service/GenericConversionService.java @@ -17,13 +17,8 @@ package org.springframework.binding.convert.service; import java.lang.reflect.Modifier; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; import java.util.Map; -import java.util.Set; import org.springframework.binding.convert.ConversionException; import org.springframework.binding.convert.ConversionExecutor; @@ -36,7 +31,10 @@ import org.springframework.binding.convert.converters.Converter; import org.springframework.binding.convert.converters.ObjectToArray; import org.springframework.binding.convert.converters.ObjectToCollection; import org.springframework.binding.convert.converters.ReverseConverter; +import org.springframework.binding.convert.converters.SpringConvertingConverterAdapter; import org.springframework.binding.convert.converters.TwoWayConverter; +import org.springframework.core.convert.converter.ConverterRegistry; +import org.springframework.format.support.FormattingConversionServiceFactoryBean; import org.springframework.util.Assert; /** @@ -47,11 +45,9 @@ import org.springframework.util.Assert; public class GenericConversionService implements ConversionService { /** - * An indexed map of converters. Each entry key is a source class that can be converted from, and each entry value - * is a map of target classes that can be converted to, ultimately mapping to a specific converter that can perform - * the source->target conversion. + * Spring ConversionService where existing custom {@link Converter} types will be registered through an adapter. */ - private final Map sourceClassConverters = new HashMap(); + private org.springframework.core.convert.ConversionService delegate; /** * A map of custom converters. Custom converters are assigned a unique identifier that can be used to lookup the @@ -69,6 +65,24 @@ public class GenericConversionService implements ConversionService { */ private ConversionService parent; + /** + * Default constructor. + */ + public GenericConversionService() { + FormattingConversionServiceFactoryBean factoryBean = new FormattingConversionServiceFactoryBean(); + factoryBean.afterPropertiesSet(); + this.delegate = factoryBean.getObject(); + } + + /** + * Constructor accepting a specific instance of a Spring ConversionService to delegate to. + * @param delegateConversionService the conversion service + */ + public GenericConversionService(org.springframework.core.convert.ConversionService delegateConversionService) { + Assert.notNull(delegateConversionService); + this.delegate = delegateConversionService; + } + /** * Returns the parent of this conversion service. Could be null. */ @@ -84,24 +98,42 @@ public class GenericConversionService implements ConversionService { } /** - * Add given converter to this conversion service. + * @return the Spring ConverterRegistry + */ + public org.springframework.core.convert.ConversionService getDelegateConversionService() { + return delegate; + } + + /** + * Registers the given converter with the underlying Spring ConversionService with the help of an adapter. The + * adapter allows an existing Spring Binding converter to be invoked within Spring's type conversion system. + * * @param converter the converter + * + * @see ConverterRegistry + * @see org.springframework.core.convert.ConversionService + * @see SpringBindingConverterAdapter */ public void addConverter(Converter converter) { - Class sourceClass = converter.getSourceClass(); - Class targetClass = converter.getTargetClass(); - Map sourceMap = getSourceMap(sourceClass); - sourceMap.put(targetClass, converter); + ((ConverterRegistry) delegate).addConverter(new SpringBindingConverterAdapter(converter)); if (converter instanceof TwoWayConverter) { - sourceMap = getSourceMap(targetClass); - sourceMap.put(sourceClass, new ReverseConverter((TwoWayConverter) converter)); + TwoWayConverter twoWayConverter = (TwoWayConverter) converter; + ((ConverterRegistry) delegate).addConverter(new SpringBindingConverterAdapter(new ReverseConverter( + twoWayConverter))); } } /** * Add given custom converter to this conversion service. + * + * Note: Converters registered through this method will not be involve the Spring type conversion system, which is + * now used the default type conversion mechanism. Spring's type conversion does not support named converters. This + * method is provided for backwards compatibility. + * * @param id the id of the custom converter instance * @param converter the converter + * + * @deprecated use {@link #addConverter(Converter)} instead */ public void addConverter(String id, Converter converter) { customConverters.put(id, converter); @@ -114,15 +146,6 @@ public class GenericConversionService implements ConversionService { aliasMap.put(alias, targetType); } - private Map getSourceMap(Class sourceClass) { - Map sourceMap = (Map) sourceClassConverters.get(sourceClass); - if (sourceMap == null) { - sourceMap = new HashMap(); - sourceClassConverters.put(sourceClass, sourceMap); - } - return sourceMap; - } - public ConversionExecutor getConversionExecutor(Class sourceClass, Class targetClass) throws ConversionExecutorNotFoundException { Assert.notNull(sourceClass, "The source class to convert from is required"); @@ -132,40 +155,15 @@ public class GenericConversionService implements ConversionService { if (targetClass.isAssignableFrom(sourceClass)) { return new StaticConversionExecutor(sourceClass, targetClass, new NoOpConverter(sourceClass, targetClass)); } - // special handling for arrays since they are not indexable classes - if (sourceClass.isArray()) { - if (targetClass.isArray()) { - return new StaticConversionExecutor(sourceClass, targetClass, new ArrayToArray(this)); - } else if (Collection.class.isAssignableFrom(targetClass)) { - if (!targetClass.isInterface() && Modifier.isAbstract(targetClass.getModifiers())) { - throw new IllegalArgumentException("Conversion target class [" + targetClass.getName() - + "] is invalid; cannot convert to abstract collection types--" - + "request an interface or concrete implementation instead"); - } - return new StaticConversionExecutor(sourceClass, targetClass, new ArrayToCollection(this)); - } - } - if (targetClass.isArray()) { - if (Collection.class.isAssignableFrom(sourceClass)) { - Converter collectionToArray = new ReverseConverter(new ArrayToCollection(this)); - return new StaticConversionExecutor(sourceClass, targetClass, collectionToArray); - } else { - return new StaticConversionExecutor(sourceClass, targetClass, new ObjectToArray(this)); - } - } - Converter converter = findRegisteredConverter(sourceClass, targetClass); - if (converter != null) { - // we found a converter - return new StaticConversionExecutor(sourceClass, targetClass, converter); + if (delegate.canConvert(sourceClass, targetClass)) { + return new StaticConversionExecutor(sourceClass, targetClass, new SpringConvertingConverterAdapter( + sourceClass, targetClass, delegate)); + } else if (parent != null) { + return parent.getConversionExecutor(sourceClass, targetClass); } else { - if (parent != null) { - // try the parent - return parent.getConversionExecutor(sourceClass, targetClass); - } else { - throw new ConversionExecutorNotFoundException(sourceClass, targetClass, - "No ConversionExecutor found for converting from sourceClass [" + sourceClass.getName() - + "] to target class [" + targetClass.getName() + "]"); - } + throw new ConversionExecutorNotFoundException(sourceClass, targetClass, + "No ConversionExecutor found for converting from sourceClass [" + sourceClass.getName() + + "] to target class [" + targetClass.getName() + "]"); } } @@ -341,46 +339,6 @@ public class GenericConversionService implements ConversionService { } } - private Converter findRegisteredConverter(Class sourceClass, Class targetClass) { - if (sourceClass.isInterface()) { - LinkedList classQueue = new LinkedList(); - classQueue.addFirst(sourceClass); - while (!classQueue.isEmpty()) { - Class currentClass = (Class) classQueue.removeLast(); - Map sourceTargetConverters = findConvertersForSource(currentClass); - Converter converter = findTargetConverter(sourceTargetConverters, targetClass); - if (converter != null) { - return converter; - } - Class[] interfaces = currentClass.getInterfaces(); - for (int i = 0; i < interfaces.length; i++) { - classQueue.addFirst(interfaces[i]); - } - } - Map objectConverters = findConvertersForSource(Object.class); - return findTargetConverter(objectConverters, targetClass); - } else { - LinkedList classQueue = new LinkedList(); - classQueue.addFirst(sourceClass); - while (!classQueue.isEmpty()) { - Class currentClass = (Class) classQueue.removeLast(); - Map sourceTargetConverters = findConvertersForSource(currentClass); - Converter converter = findTargetConverter(sourceTargetConverters, targetClass); - if (converter != null) { - return converter; - } - if (currentClass.getSuperclass() != null) { - classQueue.addFirst(currentClass.getSuperclass()); - } - Class[] interfaces = currentClass.getInterfaces(); - for (int i = 0; i < interfaces.length; i++) { - classQueue.addFirst(interfaces[i]); - } - } - return null; - } - } - public Object executeConversion(Object source, Class targetClass) throws ConversionException { if (source != null) { ConversionExecutor conversionExecutor = getConversionExecutor(source.getClass(), targetClass); @@ -412,97 +370,8 @@ public class GenericConversionService implements ConversionService { } } - // subclassing support - - public Set getConversionExecutors(Class sourceClass) { - Set parentExecutors; - if (parent != null) { - parentExecutors = parent.getConversionExecutors(sourceClass); - } else { - parentExecutors = Collections.EMPTY_SET; - } - Map sourceMap = getSourceMap(sourceClass); - if (parentExecutors.isEmpty() && sourceMap.isEmpty()) { - return Collections.EMPTY_SET; - } - Set entries = sourceMap.entrySet(); - Set conversionExecutors = new HashSet(entries.size() + parentExecutors.size()); - for (Iterator it = entries.iterator(); it.hasNext();) { - Map.Entry entry = (Map.Entry) it.next(); - Class targetClass = (Class) entry.getKey(); - Converter converter = (Converter) entry.getValue(); - conversionExecutors.add(new StaticConversionExecutor(sourceClass, targetClass, converter)); - } - conversionExecutors.addAll(parentExecutors); - return conversionExecutors; - } - - /** - * Returns an indexed map of converters. Each entry key is a source class that can be converted from, and each entry - * value is a map of target classes that can be convertered to, ultimately mapping to a specific converter that can - * perform the source->target conversion. - */ - protected Map getSourceClassConverters() { - return sourceClassConverters; - } - - /** - * Returns a registered converter object - * @param sourceClass the source class - * @param targetClass the target class - */ - protected Converter getConverter(Class sourceClass, Class targetClass) { - Map sourceTargetConverters = findConvertersForSource(sourceClass); - return findTargetConverter(sourceTargetConverters, targetClass); - } - // internal helpers - private Map findConvertersForSource(Class sourceClass) { - Map sourceConverters = (Map) sourceClassConverters.get(sourceClass); - return sourceConverters != null ? sourceConverters : Collections.EMPTY_MAP; - } - - private Converter findTargetConverter(Map sourceTargetConverters, Class targetClass) { - if (sourceTargetConverters.isEmpty()) { - return null; - } - if (targetClass.isInterface()) { - LinkedList classQueue = new LinkedList(); - classQueue.addFirst(targetClass); - while (!classQueue.isEmpty()) { - Class currentClass = (Class) classQueue.removeLast(); - Converter converter = (Converter) sourceTargetConverters.get(currentClass); - if (converter != null) { - return converter; - } - Class[] interfaces = currentClass.getInterfaces(); - for (int i = 0; i < interfaces.length; i++) { - classQueue.addFirst(interfaces[i]); - } - } - return (Converter) sourceTargetConverters.get(Object.class); - } else { - LinkedList classQueue = new LinkedList(); - classQueue.addFirst(targetClass); - while (!classQueue.isEmpty()) { - Class currentClass = (Class) classQueue.removeLast(); - Converter converter = (Converter) sourceTargetConverters.get(currentClass); - if (converter != null) { - return converter; - } - if (currentClass.getSuperclass() != null) { - classQueue.addFirst(currentClass.getSuperclass()); - } - Class[] interfaces = currentClass.getInterfaces(); - for (int i = 0; i < interfaces.length; i++) { - classQueue.addFirst(interfaces[i]); - } - } - return null; - } - } - private Class convertToWrapperClassIfNecessary(Class targetType) { if (targetType.isPrimitive()) { if (targetType.equals(int.class)) { @@ -528,4 +397,5 @@ public class GenericConversionService implements ConversionService { return targetType; } } + } \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/service/SpringBindingConverterAdapter.java b/spring-binding/src/main/java/org/springframework/binding/convert/service/SpringBindingConverterAdapter.java new file mode 100644 index 00000000..6a65eed7 --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/convert/service/SpringBindingConverterAdapter.java @@ -0,0 +1,54 @@ +/* + * Copyright 2004-2010 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.binding.convert.service; + +import java.util.Collections; +import java.util.Set; + +import org.springframework.binding.convert.converters.Converter; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.converter.GenericConverter; +import org.springframework.util.Assert; + +/** + * A Spring Converter that makes it possible for a Spring Binding Converter to be registered with a Spring + * {@link ConversionService}. + * + * @author Rossen Stoyanchev + */ +public class SpringBindingConverterAdapter implements GenericConverter { + + private Converter converter; + + public SpringBindingConverterAdapter(Converter converter) { + Assert.notNull(converter, "A Spring Binding converter is required."); + this.converter = converter; + } + + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + try { + return converter.convertSourceToTargetClass(source, targetType.getObjectType()); + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + } + + public Set getConvertibleTypes() { + return Collections.singleton(new ConvertiblePair(converter.getSourceClass(), converter.getTargetClass())); + } + +} \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpression.java b/spring-binding/src/main/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpression.java index 4348c0c0..88042b79 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpression.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpression.java @@ -15,18 +15,11 @@ */ package org.springframework.binding.expression.beanwrapper; -import java.beans.PropertyEditorSupport; -import java.util.Iterator; -import java.util.Set; - import org.springframework.beans.BeanWrapperImpl; import org.springframework.beans.BeansException; import org.springframework.beans.NotReadablePropertyException; import org.springframework.beans.NotWritablePropertyException; -import org.springframework.beans.PropertyEditorRegistry; import org.springframework.beans.TypeMismatchException; -import org.springframework.binding.convert.ConversionException; -import org.springframework.binding.convert.ConversionExecutor; import org.springframework.binding.convert.ConversionService; import org.springframework.binding.expression.EvaluationException; import org.springframework.binding.expression.Expression; @@ -95,7 +88,7 @@ public class BeanWrapperExpression implements Expression { public void setValue(Object context, Object value) { try { BeanWrapperImpl beanWrapper = new BeanWrapperImpl(context); - registerConvertersAsPropertyEditors(beanWrapper); + beanWrapper.setConversionService(conversionService.getDelegateConversionService()); beanWrapper.setPropertyValue(expression, value); } catch (NotWritablePropertyException e) { throw new PropertyNotFoundException(context.getClass(), expression, e); @@ -129,39 +122,4 @@ public class BeanWrapperExpression implements Expression { return expression; } - /** - * Adapts the String->Object converters to PropertyEditors for use during a setValue attempt. Excludes any - * String->Enum converter, since BeanWrapper has built in support for Enum conversion. - * @param registry the registry to register converter-to-editor adapters with - */ - protected void registerConvertersAsPropertyEditors(PropertyEditorRegistry registry) { - Set converters = conversionService.getConversionExecutors(String.class); - for (Iterator it = converters.iterator(); it.hasNext();) { - ConversionExecutor converter = (ConversionExecutor) it.next(); - if (!converter.getTargetClass().getName().equals("java.lang.Enum")) { - registry.registerCustomEditor(converter.getTargetClass(), new PropertyEditorConverter(converter)); - } - } - } - - private static class PropertyEditorConverter extends PropertyEditorSupport { - - private ConversionExecutor converter; - - public PropertyEditorConverter(ConversionExecutor converter) { - this.converter = converter; - } - - public void setAsText(String text) throws IllegalArgumentException { - try { - Object convertedValue = converter.execute(text); - setValue(convertedValue); - } catch (ConversionException e) { - IllegalArgumentException iae = new IllegalArgumentException("Unable to convert text '" + text + "'"); - iae.initCause(e); - throw iae; - } - } - } - } diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/spel/SpringELExpression.java b/spring-binding/src/main/java/org/springframework/binding/expression/spel/SpringELExpression.java index 4543b56a..cd59e347 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/spel/SpringELExpression.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/spel/SpringELExpression.java @@ -149,4 +149,8 @@ public class SpringELExpression implements Expression { return variableValues; } + public String toString() { + return getExpressionString(); + } + } diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/spel/SpringELExpressionParser.java b/spring-binding/src/main/java/org/springframework/binding/expression/spel/SpringELExpressionParser.java index a02d5080..34eb8663 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/spel/SpringELExpressionParser.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/spel/SpringELExpressionParser.java @@ -16,11 +16,12 @@ package org.springframework.binding.expression.spel; import java.util.ArrayList; -import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.springframework.binding.convert.ConversionService; +import org.springframework.binding.convert.service.DefaultConversionService; import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.ExpressionParser; import org.springframework.binding.expression.ExpressionVariable; @@ -28,16 +29,11 @@ import org.springframework.binding.expression.ParserContext; import org.springframework.binding.expression.ParserException; import org.springframework.binding.expression.support.NullParserContext; import org.springframework.context.expression.MapAccessor; -import org.springframework.core.convert.ConversionService; import org.springframework.expression.EvaluationContext; import org.springframework.expression.PropertyAccessor; -import org.springframework.expression.TypeConverter; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardTypeConverter; -import org.springframework.format.FormatterRegistry; -import org.springframework.format.datetime.DateFormatter; -import org.springframework.format.support.FormattingConversionServiceFactoryBean; import org.springframework.util.Assert; /** @@ -57,29 +53,17 @@ public class SpringELExpressionParser implements ExpressionParser { private List propertyAccessors = new ArrayList(); public SpringELExpressionParser(SpelExpressionParser expressionParser) { + this(expressionParser, new DefaultConversionService()); + } + + public SpringELExpressionParser(SpelExpressionParser expressionParser, ConversionService conversionService) { this.expressionParser = expressionParser; this.propertyAccessors.add(new MapAccessor()); - } - - public ConversionService getConversionService() { - ensureConversionServiceInitialized(); - return conversionService; - } - - public void setConversionService(ConversionService conversionService) { this.conversionService = conversionService; } - private void ensureConversionServiceInitialized() { - if (this.conversionService == null) { - FormattingConversionServiceFactoryBean factoryBean = new FormattingConversionServiceFactoryBean() { - protected void installFormatters(FormatterRegistry registry) { - registry.addFormatterForFieldType(Date.class, new DateFormatter()); - } - }; - factoryBean.afterPropertiesSet(); - this.conversionService = factoryBean.getObject(); - } + public ConversionService getConversionService() { + return conversionService; } public void addPropertyAccessor(PropertyAccessor propertyAccessor) { @@ -90,17 +74,13 @@ public class SpringELExpressionParser implements ExpressionParser { Assert.hasText(expressionString, "The expression string to parse is required and must not be empty"); parserContext = (parserContext == null) ? NullParserContext.INSTANCE : parserContext; StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); - evaluationContext.setTypeConverter(getTypeConverter()); + evaluationContext.setTypeConverter(new StandardTypeConverter(conversionService.getDelegateConversionService())); evaluationContext.getPropertyAccessors().addAll(propertyAccessors); Map spelExpressionVariables = parseSpelExpressionVariables(parserContext.getExpressionVariables()); return new SpringELExpression(parseSpelExpression(expressionString, parserContext), spelExpressionVariables, parserContext.getExpectedEvaluationResultType(), evaluationContext); } - private TypeConverter getTypeConverter() { - return (conversionService != null) ? new StandardTypeConverter(conversionService) : new StandardTypeConverter(); - } - private org.springframework.expression.Expression parseSpelExpression(String expression, ParserContext parserContext) { return expressionParser.parseExpression(expression, getSpelParserContext(parserContext)); } diff --git a/spring-binding/src/test/java/org/springframework/binding/convert/service/DefaultConversionServiceTests.java b/spring-binding/src/test/java/org/springframework/binding/convert/service/DefaultConversionServiceTests.java index 966ef0f3..7f45b9ac 100644 --- a/spring-binding/src/test/java/org/springframework/binding/convert/service/DefaultConversionServiceTests.java +++ b/spring-binding/src/test/java/org/springframework/binding/convert/service/DefaultConversionServiceTests.java @@ -15,13 +15,11 @@ */ package org.springframework.binding.convert.service; -import java.math.BigDecimal; import java.security.Principal; import java.util.AbstractList; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; -import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; @@ -60,7 +58,6 @@ public class DefaultConversionServiceTests extends TestCase { DefaultConversionService service = new DefaultConversionService(); StaticConversionExecutor executor = (StaticConversionExecutor) service.getConversionExecutor(String.class, Boolean.class); - assertNotSame(customConverter, executor.getConverter()); try { executor.execute("ja"); fail(); @@ -69,7 +66,6 @@ public class DefaultConversionServiceTests extends TestCase { } service.addConverter(customConverter); executor = (StaticConversionExecutor) service.getConversionExecutor(String.class, Boolean.class); - assertSame(customConverter, executor.getConverter()); assertTrue(((Boolean) executor.execute("ja")).booleanValue()); } @@ -510,16 +506,20 @@ public class DefaultConversionServiceTests extends TestCase { DefaultConversionService service = new DefaultConversionService(); ConversionExecutor executor = service.getConversionExecutor(String.class, String[].class); String[] result = (String[]) executor.execute("1,2,3"); - assertEquals(1, result.length); - assertEquals("1,2,3", result[0]); + assertEquals(3, result.length); + assertEquals("1", result[0]); + assertEquals("2", result[1]); + assertEquals("3", result[2]); } public void testStringToListConversion() { DefaultConversionService service = new DefaultConversionService(); ConversionExecutor executor = service.getConversionExecutor(String.class, List.class); List result = (List) executor.execute("1,2,3"); - assertEquals(1, result.size()); - assertEquals("1,2,3", result.get(0)); + assertEquals(3, result.size()); + assertEquals("1", result.get(0)); + assertEquals("2", result.get(1)); + assertEquals("3", result.get(2)); } public void testStringToArrayConversionWithElementConversion() { @@ -530,25 +530,6 @@ public class DefaultConversionServiceTests extends TestCase { assertEquals(new Integer(123), result[0]); } - public void testGetConversionExecutorsForSource() { - DefaultConversionService service1 = new DefaultConversionService(); - service1.addConverter(new CustomConverter()); - GenericConversionService service2 = new GenericConversionService(); - FormattedStringToNumber formatterConverter = new FormattedStringToNumber(BigDecimal.class); - service2.addConverter(formatterConverter); - service2.setParent(service1); - Set converters = service2.getConversionExecutors(String.class); - Iterator it = converters.iterator(); - while (it.hasNext()) { - ConversionExecutor executor = (ConversionExecutor) it.next(); - if (executor.getTargetClass().equals(BigDecimal.class)) { - StaticConversionExecutor se = (StaticConversionExecutor) executor; - assertSame(formatterConverter, se.getConverter()); - } - } - assertEquals(15, converters.size()); - } - private static class CustomConverter implements Converter { public Object convertSourceToTargetClass(final Object source, Class targetClass) throws Exception { diff --git a/spring-binding/src/test/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpressionParserTests.java b/spring-binding/src/test/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpressionParserTests.java index 7a80aec4..8a52f12a 100644 --- a/spring-binding/src/test/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpressionParserTests.java +++ b/spring-binding/src/test/java/org/springframework/binding/expression/beanwrapper/BeanWrapperExpressionParserTests.java @@ -18,6 +18,8 @@ package org.springframework.binding.expression.beanwrapper; import junit.framework.TestCase; import org.springframework.beans.TypeMismatchException; +import org.springframework.binding.convert.converters.StringToDate; +import org.springframework.binding.convert.service.GenericConversionService; import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.ParserException; import org.springframework.binding.expression.ValueCoercionException; @@ -99,6 +101,10 @@ public class BeanWrapperExpressionParserTests extends TestCase { } public void testSetValueWithCoersion() { + GenericConversionService cs = (GenericConversionService) parser.getConversionService(); + StringToDate converter = new StringToDate(); + converter.setPattern("yyyy-MM-dd"); + cs.addConverter(converter); Expression e = parser.parseExpression("date", null); e.setValue(bean, "2008-9-15"); } diff --git a/spring-binding/src/test/java/org/springframework/binding/expression/ognl/OgnlExpressionParserTests.java b/spring-binding/src/test/java/org/springframework/binding/expression/ognl/OgnlExpressionParserTests.java index 10500e2e..19c75b40 100644 --- a/spring-binding/src/test/java/org/springframework/binding/expression/ognl/OgnlExpressionParserTests.java +++ b/spring-binding/src/test/java/org/springframework/binding/expression/ognl/OgnlExpressionParserTests.java @@ -17,6 +17,8 @@ package org.springframework.binding.expression.ognl; import junit.framework.TestCase; +import org.springframework.binding.convert.converters.StringToDate; +import org.springframework.binding.convert.service.GenericConversionService; import org.springframework.binding.expression.EvaluationException; import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.ExpressionVariable; @@ -203,6 +205,10 @@ public class OgnlExpressionParserTests extends TestCase { } public void testSetValueWithCoersion() { + GenericConversionService cs = (GenericConversionService) parser.getConversionService(); + StringToDate converter = new StringToDate(); + converter.setPattern("yyyy-MM-dd"); + cs.addConverter(converter); Expression e = parser.parseExpression("date", null); e.setValue(bean, "2008-9-15"); } diff --git a/spring-faces/src/main/java/org/springframework/faces/config/FacesFlowBuilderServicesBeanDefinitionParser.java b/spring-faces/src/main/java/org/springframework/faces/config/FacesFlowBuilderServicesBeanDefinitionParser.java index 91ec1e94..0b1d1c66 100644 --- a/spring-faces/src/main/java/org/springframework/faces/config/FacesFlowBuilderServicesBeanDefinitionParser.java +++ b/spring-faces/src/main/java/org/springframework/faces/config/FacesFlowBuilderServicesBeanDefinitionParser.java @@ -16,6 +16,7 @@ package org.springframework.faces.config; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.parsing.CompositeComponentDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; @@ -123,6 +124,7 @@ public class FacesFlowBuilderServicesBeanDefinitionParser extends AbstractSingle } expressionParserBuilder.addConstructorArgValue(spelExpressionParser.getBeanDefinition()); + expressionParserBuilder.addConstructorArgReference(getConversionService(definitionBuilder)); expressionParser = registerInfrastructureComponent(element, context, expressionParserBuilder); } else if (enableManagedBeans) { @@ -141,6 +143,12 @@ public class FacesFlowBuilderServicesBeanDefinitionParser extends AbstractSingle } } + private String getConversionService(BeanDefinitionBuilder definitionBuilder) { + RuntimeBeanReference conversionServiceReference = (RuntimeBeanReference) definitionBuilder.getBeanDefinition() + .getPropertyValues().getPropertyValue(CONVERSION_SERVICE_PROPERTY).getValue(); + return conversionServiceReference.getBeanName(); + } + private String registerInfrastructureComponent(Element element, ParserContext context, BeanDefinitionBuilder componentBuilder) { String beanName = context.getReaderContext().generateBeanName(componentBuilder.getRawBeanDefinition()); diff --git a/spring-faces/src/main/java/org/springframework/faces/model/converter/DataModelConverter.java b/spring-faces/src/main/java/org/springframework/faces/model/converter/DataModelConverter.java index ffd16fc1..af804548 100644 --- a/spring-faces/src/main/java/org/springframework/faces/model/converter/DataModelConverter.java +++ b/spring-faces/src/main/java/org/springframework/faces/model/converter/DataModelConverter.java @@ -33,7 +33,7 @@ import org.springframework.util.ClassUtils; public class DataModelConverter implements Converter { public Class getSourceClass() { - return Object.class; + return List.class; } public Class getTargetClass() { diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/FacesSpringELExpressionParser.java b/spring-faces/src/main/java/org/springframework/faces/webflow/FacesSpringELExpressionParser.java index 6faeef3a..7ec2e51f 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/FacesSpringELExpressionParser.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/FacesSpringELExpressionParser.java @@ -15,6 +15,7 @@ */ package org.springframework.faces.webflow; +import org.springframework.binding.convert.ConversionService; import org.springframework.binding.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.webflow.expression.spel.WebFlowSpringELExpressionParser; @@ -34,4 +35,9 @@ public class FacesSpringELExpressionParser extends WebFlowSpringELExpressionPars addPropertyAccessor(new JsfManagedBeanPropertyAccessor()); } + public FacesSpringELExpressionParser(SpelExpressionParser expressionParser, ConversionService conversionService) { + super(expressionParser, conversionService); + addPropertyAccessor(new JsfManagedBeanPropertyAccessor()); + } + } diff --git a/spring-faces/src/test/java/org/springframework/faces/config/FacesFlowBuilderServicesBeanDefinitionParserTests.java b/spring-faces/src/test/java/org/springframework/faces/config/FacesFlowBuilderServicesBeanDefinitionParserTests.java index b47d33be..0205c536 100644 --- a/spring-faces/src/test/java/org/springframework/faces/config/FacesFlowBuilderServicesBeanDefinitionParserTests.java +++ b/spring-faces/src/test/java/org/springframework/faces/config/FacesFlowBuilderServicesBeanDefinitionParserTests.java @@ -70,7 +70,7 @@ public class FacesFlowBuilderServicesBeanDefinitionParserTests extends TestCase assertNotNull(builderServices); assertTrue(builderServices.getConversionService() instanceof TestConversionService); assertTrue(builderServices.getExpressionParser() instanceof WebFlowSpringELExpressionParser); - assertNotNull(((SpringELExpressionParser) builderServices.getExpressionParser()).getConversionService()); + assertTrue(((SpringELExpressionParser) builderServices.getExpressionParser()).getConversionService() instanceof TestConversionService); assertTrue(builderServices.getViewFactoryCreator() instanceof JsfViewFactoryCreator); assertFalse(builderServices.getDevelopment()); } @@ -115,5 +115,9 @@ public class FacesFlowBuilderServicesBeanDefinitionParserTests extends TestCase public Class getClassForAlias(String name) throws ConversionExecutionException { throw new UnsupportedOperationException("Auto-generated method stub"); } + + public org.springframework.core.convert.ConversionService getDelegateConversionService() { + throw new UnsupportedOperationException("Auto-generated method stub"); + } } } diff --git a/spring-webflow-samples/booking-faces/ivy.xml b/spring-webflow-samples/booking-faces/ivy.xml index b6288d83..726f65a3 100755 --- a/spring-webflow-samples/booking-faces/ivy.xml +++ b/spring-webflow-samples/booking-faces/ivy.xml @@ -32,11 +32,12 @@ - + + diff --git a/spring-webflow-samples/booking-faces/pom.xml b/spring-webflow-samples/booking-faces/pom.xml index bd032cff..52f899f0 100644 --- a/spring-webflow-samples/booking-faces/pom.xml +++ b/spring-webflow-samples/booking-faces/pom.xml @@ -53,9 +53,9 @@ 1.8.0.9 - org.jboss.el - com.springsource.org.jboss.el - 2.0.0.GA + joda-time + joda-time + 1.6 org.springframework @@ -77,6 +77,11 @@ org.springframework.core ${spring.version} + + org.springframework + org.springframework.expression + ${spring.version} + org.springframework org.springframework.jdbc diff --git a/spring-webflow-samples/booking-mvc/ivy.xml b/spring-webflow-samples/booking-mvc/ivy.xml index 51f3c8ea..3be23c88 100755 --- a/spring-webflow-samples/booking-mvc/ivy.xml +++ b/spring-webflow-samples/booking-mvc/ivy.xml @@ -36,12 +36,13 @@ - + + diff --git a/spring-webflow-samples/booking-mvc/pom.xml b/spring-webflow-samples/booking-mvc/pom.xml index 2a63856d..1db82fb7 100644 --- a/spring-webflow-samples/booking-mvc/pom.xml +++ b/spring-webflow-samples/booking-mvc/pom.xml @@ -73,9 +73,9 @@ 1.8.0.9 - org.ognl - com.springsource.org.ognl - 2.6.9 + joda-time + joda-time + 1.6 org.springframework @@ -102,6 +102,11 @@ org.springframework.core ${spring.version} + + org.springframework + org.springframework.expression + ${spring.version} + org.springframework org.springframework.jdbc diff --git a/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/ApplicationConversionService.java b/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/ApplicationConversionService.java deleted file mode 100644 index b921bf13..00000000 --- a/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/ApplicationConversionService.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.springframework.webflow.samples.booking; - -import org.springframework.binding.convert.converters.StringToDate; -import org.springframework.binding.convert.service.DefaultConversionService; -import org.springframework.stereotype.Component; - -@Component("conversionService") -public class ApplicationConversionService extends DefaultConversionService { - - @Override - protected void addDefaultConverters() { - super.addDefaultConverters(); - StringToDate dateConverter = new StringToDate(); - dateConverter.setPattern("MM-dd-yyyy"); - addConverter("shortDate", dateConverter); - } - -} \ No newline at end of file diff --git a/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/Booking.java b/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/Booking.java index ecc1db34..05b60da9 100755 --- a/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/Booking.java +++ b/spring-webflow-samples/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/Booking.java @@ -20,6 +20,7 @@ import javax.persistence.Transient; import org.springframework.binding.message.MessageBuilder; import org.springframework.binding.message.MessageContext; import org.springframework.binding.validation.ValidationContext; +import org.springframework.format.annotation.DateTimeFormat; /** * A Hotel Booking made by a User. @@ -32,8 +33,10 @@ public class Booking implements Serializable { private Hotel hotel; + @DateTimeFormat(pattern = "MM-dd-yyyy") private Date checkinDate; + @DateTimeFormat(pattern = "MM-dd-yyyy") private Date checkoutDate; private String creditCard; @@ -73,7 +76,7 @@ public class Booking implements Serializable { if (checkinDate == null || checkoutDate == null) { return 0; } else { - return (int) (checkoutDate.getTime() - checkinDate.getTime()) / 1000 / 60 / 60 / 24; + return (int) ((checkoutDate.getTime() - checkinDate.getTime()) / 1000 / 60 / 60 / 24); } } diff --git a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/config/webflow-config.xml b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/config/webflow-config.xml index 9ff6bd1a..d6dfa19f 100644 --- a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/config/webflow-config.xml +++ b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/config/webflow-config.xml @@ -22,8 +22,8 @@ - - + + diff --git a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/config/webmvc-config.xml b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/config/webmvc-config.xml index 7b85d66d..d0f4eecd 100644 --- a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/config/webmvc-config.xml +++ b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/config/webmvc-config.xml @@ -1,9 +1,9 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:mvc="http://www.springframework.org/schema/mvc" + xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> diff --git a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/booking-flow.xml b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/booking-flow.xml index feb4f54b..b97f51ff 100644 --- a/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/booking-flow.xml +++ b/spring-webflow-samples/booking-mvc/src/main/webapp/WEB-INF/hotels/booking/booking-flow.xml @@ -10,13 +10,13 @@ - + - - + + diff --git a/spring-webflow-samples/booking-portlet-faces/ivy.xml b/spring-webflow-samples/booking-portlet-faces/ivy.xml index aafd3538..8b5aa945 100644 --- a/spring-webflow-samples/booking-portlet-faces/ivy.xml +++ b/spring-webflow-samples/booking-portlet-faces/ivy.xml @@ -34,11 +34,12 @@ - + + diff --git a/spring-webflow-samples/booking-portlet-faces/pom.xml b/spring-webflow-samples/booking-portlet-faces/pom.xml index 33a02a67..8008b914 100644 --- a/spring-webflow-samples/booking-portlet-faces/pom.xml +++ b/spring-webflow-samples/booking-portlet-faces/pom.xml @@ -58,9 +58,9 @@ 1.8.0.9 - org.jboss.el - com.springsource.org.jboss.el - 2.0.0.GA + joda-time + joda-time + 1.6 org.springframework @@ -82,6 +82,11 @@ org.springframework.core ${spring.version} + + org.springframework + org.springframework.expression + ${spring.version} + org.springframework org.springframework.jdbc diff --git a/spring-webflow-samples/booking-portlet-mvc/ivy.xml b/spring-webflow-samples/booking-portlet-mvc/ivy.xml index 68c46e87..caa751cd 100644 --- a/spring-webflow-samples/booking-portlet-mvc/ivy.xml +++ b/spring-webflow-samples/booking-portlet-mvc/ivy.xml @@ -21,7 +21,6 @@ - @@ -34,11 +33,12 @@ - + + diff --git a/spring-webflow-samples/booking-portlet-mvc/pom.xml b/spring-webflow-samples/booking-portlet-mvc/pom.xml index d54f3ec3..5d9ae567 100644 --- a/spring-webflow-samples/booking-portlet-mvc/pom.xml +++ b/spring-webflow-samples/booking-portlet-mvc/pom.xml @@ -48,9 +48,9 @@ 1.8.0.9 - org.jboss.el - com.springsource.org.jboss.el - 2.0.0.GA + joda-time + joda-time + 1.6 org.springframework @@ -72,6 +72,11 @@ org.springframework.core ${spring.version} + + org.springframework + org.springframework.expression + ${spring.version} + org.springframework org.springframework.jdbc @@ -119,12 +124,6 @@ 2.0.0 provided - - javax.el - com.springsource.javax.el - 1.0.0 - provided - javax.servlet com.springsource.javax.servlet diff --git a/spring-webflow-samples/booking-portlet-mvc/src/main/java/org/springframework/webflow/samples/booking/Booking.java b/spring-webflow-samples/booking-portlet-mvc/src/main/java/org/springframework/webflow/samples/booking/Booking.java index e44bc8ab..bd24b722 100644 --- a/spring-webflow-samples/booking-portlet-mvc/src/main/java/org/springframework/webflow/samples/booking/Booking.java +++ b/spring-webflow-samples/booking-portlet-mvc/src/main/java/org/springframework/webflow/samples/booking/Booking.java @@ -16,6 +16,8 @@ import javax.persistence.Temporal; import javax.persistence.TemporalType; import javax.persistence.Transient; +import org.springframework.format.annotation.DateTimeFormat; + /** * A Hotel Booking made by a User. */ @@ -27,8 +29,10 @@ public class Booking implements Serializable { private Hotel hotel; + @DateTimeFormat(pattern = "MM-dd-yyyy") private Date checkinDate; + @DateTimeFormat(pattern = "MM-dd-yyyy") private Date checkoutDate; private String creditCard; diff --git a/spring-webflow-samples/jsf-booking/ivy.xml b/spring-webflow-samples/jsf-booking/ivy.xml index 2c7940dc..78dc3fba 100644 --- a/spring-webflow-samples/jsf-booking/ivy.xml +++ b/spring-webflow-samples/jsf-booking/ivy.xml @@ -21,32 +21,33 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - + + diff --git a/spring-webflow-samples/jsf-booking/pom.xml b/spring-webflow-samples/jsf-booking/pom.xml index af54febf..4c8a04d3 100644 --- a/spring-webflow-samples/jsf-booking/pom.xml +++ b/spring-webflow-samples/jsf-booking/pom.xml @@ -63,9 +63,9 @@ 1.8.0.9 - org.jboss.el - com.springsource.org.jboss.el - 2.0.0.GA + joda-time + joda-time + 1.6 org.springframework @@ -87,6 +87,11 @@ org.springframework.core ${spring.version} + + org.springframework + org.springframework.expression + ${spring.version} + org.springframework org.springframework.jdbc diff --git a/spring-webflow/src/main/java/org/springframework/webflow/action/ActionResultExposer.java b/spring-webflow/src/main/java/org/springframework/webflow/action/ActionResultExposer.java deleted file mode 100644 index 431d146d..00000000 --- a/spring-webflow/src/main/java/org/springframework/webflow/action/ActionResultExposer.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2004-2008 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.webflow.action; - -import java.io.Serializable; - -import org.springframework.binding.convert.ConversionService; -import org.springframework.binding.expression.Expression; -import org.springframework.core.style.ToStringCreator; -import org.springframework.util.Assert; -import org.springframework.webflow.execution.RequestContext; - -/** - * Specifies how an action result value should be exposed to an executing flow. The return value is exposed as an - * attribute in a configured scope. - * - * @see EvaluateAction - * - * @author Keith Donald - */ -public class ActionResultExposer implements Serializable { - - /** - * The expression to set the result to. - */ - private Expression resultExpression; - - /** - * The desired type to expose the result as - */ - private Class expectedResultType; - - /** - * The {@link ConversionService} to use to convert the result to the desired type - */ - private ConversionService conversionService; - - /** - * Creates a action result exposer - * @param resultExpression the result expression - * @param expectedResultType the expected result type - */ - public ActionResultExposer(Expression resultExpression, Class expectedResultType, - ConversionService conversionService) { - Assert.notNull(resultExpression, "The result expression is required"); - if (expectedResultType != null) { - Assert.notNull(conversionService, "A conversionService is required with an expectedResultType"); - } - this.resultExpression = resultExpression; - this.expectedResultType = expectedResultType; - this.conversionService = conversionService; - } - - /** - * Returns name of the attribute to index the return value with. - */ - public Expression getNameExpression() { - return resultExpression; - } - - /** - * Returns the desired result type to be exposed - */ - public Class getExpectedResultType() { - return expectedResultType; - } - - /** - * Expose given bean method return value in given flow execution request context. - * @param result the return value - * @param context the request context - */ - public void exposeResult(Object result, RequestContext context) { - resultExpression.setValue(context, applyTypeConversionIfNecessary(result)); - } - - /** - * Apply type conversion on the supplied value if necessary. - * @param value the raw value to be converted - */ - private Object applyTypeConversionIfNecessary(Object value) { - if (value == null || expectedResultType == null) { - return value; - } else { - return conversionService.getConversionExecutor(value.getClass(), expectedResultType).execute(value); - } - } - - public String toString() { - return new ToStringCreator(this).append("result", resultExpression).append("resultType", expectedResultType) - .toString(); - } -} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/action/EvaluateAction.java b/spring-webflow/src/main/java/org/springframework/webflow/action/EvaluateAction.java index 0f8855ca..a16e1846 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/action/EvaluateAction.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/action/EvaluateAction.java @@ -30,7 +30,6 @@ import org.springframework.webflow.execution.RequestContext; * {@link Event}. * * @see Expression - * @see ActionResultExposer * @see ResultEventFactory * * @author Keith Donald @@ -44,9 +43,9 @@ public class EvaluateAction extends AbstractAction { private Expression expression; /** - * The helper that will expose the expression evaluation result. Optional. + * The expression to evaluate to set the result of the action. Optional. */ - private ActionResultExposer evaluationResultExposer; + private Expression resultExpression; /** * The selector for the factory that will create the action result event callers can respond to. @@ -56,10 +55,10 @@ public class EvaluateAction extends AbstractAction { /** * Create a new evaluate action. * @param expression the expression to evaluate (required) - * @param evaluationResultExposer the strategy for how the expression result will be exposed to the flow (optional) + * @param resultExpression the expression to evaluate the result (optional) */ - public EvaluateAction(Expression expression, ActionResultExposer evaluationResultExposer) { - init(expression, evaluationResultExposer, null); + public EvaluateAction(Expression expression, Expression resultExpression) { + init(expression, resultExpression, null); } /** @@ -68,9 +67,8 @@ public class EvaluateAction extends AbstractAction { * @param evaluationResultExposer the strategy for how the expression result will be exposed to the flow (optional) * @param resultEventFactory the factory that will map the evaluation result to a Web Flow event (optional) */ - public EvaluateAction(Expression expression, ActionResultExposer evaluationResultExposer, - ResultEventFactory resultEventFactory) { - init(expression, evaluationResultExposer, resultEventFactory); + public EvaluateAction(Expression expression, Expression resultExpression, ResultEventFactory resultEventFactory) { + init(expression, resultExpression, resultEventFactory); } protected Event doExecute(RequestContext context) throws Exception { @@ -78,25 +76,24 @@ public class EvaluateAction extends AbstractAction { if (result instanceof Action) { return ActionExecutor.execute((Action) result, context); } else { - if (evaluationResultExposer != null) { - evaluationResultExposer.exposeResult(result, context); + if (resultExpression != null) { + resultExpression.setValue(context, result); } return resultEventFactory.createResultEvent(this, result, context); } } public String toString() { - return new ToStringCreator(this).append("expression", expression).append("resultExposer", - evaluationResultExposer).toString(); + return new ToStringCreator(this).append("expression", expression).append("resultExpression", resultExpression) + .toString(); } // internal helpers - private void init(Expression expression, ActionResultExposer evaluationResultExposer, - ResultEventFactory resultEventFactory) { + private void init(Expression expression, Expression resultExpression, ResultEventFactory resultEventFactory) { Assert.notNull(expression, "The expression this action should evaluate is required"); this.expression = expression; - this.evaluationResultExposer = evaluationResultExposer; + this.resultExpression = resultExpression; this.resultEventFactory = resultEventFactory != null ? resultEventFactory : new DefaultResultEventFactory(); } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/action/SetAction.java b/spring-webflow/src/main/java/org/springframework/webflow/action/SetAction.java index 71c3a78d..295ce308 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/action/SetAction.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/action/SetAction.java @@ -15,7 +15,6 @@ */ package org.springframework.webflow.action; -import org.springframework.binding.convert.ConversionService; import org.springframework.binding.expression.Expression; import org.springframework.core.style.ToStringCreator; import org.springframework.util.Assert; @@ -40,58 +39,26 @@ public class SetAction extends AbstractAction { */ private Expression valueExpression; - /** - * The expected value type. - */ - private Class expectedType; - - /** - * The service to perform the type conversion if the actual value type does not match the expected. - */ - private ConversionService conversionService; - /** * Creates a new set attribute action. * @param nameExpression the name of the property to set (required) - * @param valueExpression the expression to obtain the new property value (required) - * @param expectedType the expected value type - * @param conversionService the service to perform the type conversion if the actual value type does not match the - * expected + * @param valueExpression the expression to obtain the new property value (required) expected */ - public SetAction(Expression nameExpression, Expression valueExpression, Class expectedType, - ConversionService conversionService) { + public SetAction(Expression nameExpression, Expression valueExpression) { Assert.notNull(nameExpression, "The name expression is required"); Assert.notNull(valueExpression, "The value expression is required"); - if (expectedType != null) { - Assert.notNull(conversionService, "The conversion service is required if the expectedType is provided"); - } this.nameExpression = nameExpression; this.valueExpression = valueExpression; - this.expectedType = expectedType; - this.conversionService = conversionService; } protected Event doExecute(RequestContext context) throws Exception { Object value = valueExpression.getValue(context); - nameExpression.setValue(context, applyTypeConversionIfNecessary(value)); + nameExpression.setValue(context, value); return success(); } - /** - * Apply type conversion on the supplied value if necessary. - * @param value the raw value to be converted - */ - private Object applyTypeConversionIfNecessary(Object value) { - if (value == null || expectedType == null) { - return value; - } else { - return conversionService.getConversionExecutor(value.getClass(), expectedType).execute(value); - } - } - public String toString() { - return new ToStringCreator(this).append("name", nameExpression).append("value", valueExpression).append("type", - expectedType).toString(); + return new ToStringCreator(this).append("name", nameExpression).append("value", valueExpression).toString(); } } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/FlowBuilderServicesBeanDefinitionParser.java b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowBuilderServicesBeanDefinitionParser.java index a79eb8ed..878931c6 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/config/FlowBuilderServicesBeanDefinitionParser.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowBuilderServicesBeanDefinitionParser.java @@ -16,6 +16,7 @@ package org.springframework.webflow.config; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.parsing.CompositeComponentDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; @@ -88,6 +89,7 @@ class FlowBuilderServicesBeanDefinitionParser extends AbstractSingleBeanDefiniti .genericBeanDefinition(WEB_FLOW_SPRING_EL_EXPRESSION_PARSER_CLASS_NAME); webFlowElExpressionParserBuilder .addConstructorArgValue(springElExpressionParserBuilder.getBeanDefinition()); + webFlowElExpressionParserBuilder.addConstructorArgReference(getConversionService(definitionBuilder)); expressionParser = registerInfrastructureComponent(element, context, webFlowElExpressionParserBuilder); } definitionBuilder.addPropertyReference(EXPRESSION_PARSER_PROPERTY, expressionParser); @@ -110,6 +112,12 @@ class FlowBuilderServicesBeanDefinitionParser extends AbstractSingleBeanDefiniti } } + private String getConversionService(BeanDefinitionBuilder definitionBuilder) { + RuntimeBeanReference conversionServiceReference = (RuntimeBeanReference) definitionBuilder.getBeanDefinition() + .getPropertyValues().getPropertyValue(CONVERSION_SERVICE_PROPERTY).getValue(); + return conversionServiceReference.getBeanName(); + } + private String registerInfrastructureComponent(Element element, ParserContext context, BeanDefinitionBuilder componentBuilder) { String beanName = context.getReaderContext().generateBeanName(componentBuilder.getRawBeanDefinition()); diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/BinderConfiguration.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/BinderConfiguration.java index dedfe66a..9b6a758a 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/BinderConfiguration.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/BinderConfiguration.java @@ -50,6 +50,22 @@ public class BinderConfiguration { return null; } + /** + * Gets the converterId for the binding with the specified name. Returns null if either a binding or a converterId + * for the given name is not found. + * + * @param name the name of the binding. + * @return the binding + */ + public String getConverterId(String name) { + Binding binding = getBinding(name); + if (binding != null) { + return binding.getConverter(); + } else { + return null; + } + } + /** * A binding that provides the information needed to connect an element of the view to a property of the model. * diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilder.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilder.java index a512b957..c82164cb 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilder.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilder.java @@ -48,7 +48,6 @@ import org.springframework.util.StringUtils; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.RequestScope; import org.springframework.web.context.support.GenericWebApplicationContext; -import org.springframework.webflow.action.ActionResultExposer; import org.springframework.webflow.action.EvaluateAction; import org.springframework.webflow.action.ExternalRedirectAction; import org.springframework.webflow.action.FlowDefinitionRedirectAction; @@ -342,7 +341,7 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder { flowContext.getBeanFactory().registerScope("conversation", new ConversationScope()); Resource flowResource = flowModelHolder.getFlowModelResource(); flowContext.setResourceLoader(new FlowRelativeResourceLoader(flowResource)); - if (JdkVersion.isAtLeastJava15()) { + if (JdkVersion.getMajorJavaVersion() >= JdkVersion.JAVA_15) { AnnotationConfigUtils.registerAnnotationConfigProcessors(flowContext); } new XmlBeanDefinitionReader(flowContext).loadBeanDefinitions(resources); @@ -852,25 +851,18 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder { } private Action parseEvaluateAction(EvaluateModel evaluate) { - String expressionString = evaluate.getExpression(); - Expression expression = getLocalContext().getExpressionParser().parseExpression(expressionString, - new FluentParserContext().evaluate(RequestContext.class)); - return new EvaluateAction(expression, parseEvaluationActionResultExposer(evaluate)); - } - - private ActionResultExposer parseEvaluationActionResultExposer(EvaluateModel evaluate) { - if (StringUtils.hasText(evaluate.getResult())) { - Expression resultExpression = getLocalContext().getExpressionParser().parseExpression(evaluate.getResult(), - new FluentParserContext().evaluate(RequestContext.class)); - Class expectedResultType = null; - if (StringUtils.hasText(evaluate.getResultType())) { - expectedResultType = toClass(evaluate.getResultType()); - } - return new ActionResultExposer(resultExpression, expectedResultType, getLocalContext() - .getConversionService()); - } else { - return null; + FluentParserContext evaluateExpressionParserContext = new FluentParserContext().evaluate(RequestContext.class); + if (StringUtils.hasText(evaluate.getResultType())) { + evaluateExpressionParserContext.expectResult(toClass(evaluate.getResultType())); } + Expression evaluateExpression = getLocalContext().getExpressionParser().parseExpression( + evaluate.getExpression(), evaluateExpressionParserContext); + Expression resultExpression = null; + if (StringUtils.hasText(evaluate.getResult())) { + resultExpression = getLocalContext().getExpressionParser().parseExpression(evaluate.getResult(), + new FluentParserContext().evaluate(RequestContext.class)); + } + return new EvaluateAction(evaluateExpression, resultExpression); } private Action parseRenderAction(RenderModel render) { @@ -889,13 +881,13 @@ public class FlowModelFlowBuilder extends AbstractFlowBuilder { private Action parseSetAction(SetModel set) { Expression nameExpression = getLocalContext().getExpressionParser().parseExpression(set.getName(), new FluentParserContext().evaluate(RequestContext.class)); - Expression valueExpression = getLocalContext().getExpressionParser().parseExpression(set.getValue(), - new FluentParserContext().evaluate(RequestContext.class)); - Class expectedType = null; + FluentParserContext valueParserContext = new FluentParserContext().evaluate(RequestContext.class); if (StringUtils.hasText(set.getType())) { - expectedType = toClass(set.getType()); + valueParserContext.expectResult(toClass(set.getType())); } - return new SetAction(nameExpression, valueExpression, expectedType, getLocalContext().getConversionService()); + Expression valueExpression = getLocalContext().getExpressionParser().parseExpression(set.getValue(), + valueParserContext); + return new SetAction(nameExpression, valueExpression); } private MutableAttributeMap parseMetaAttributes(List attributeModels) { diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/support/FlowBuilderContextImpl.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/support/FlowBuilderContextImpl.java index 559e49af..d2566a86 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/support/FlowBuilderContextImpl.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/support/FlowBuilderContextImpl.java @@ -15,8 +15,6 @@ */ package org.springframework.webflow.engine.builder.support; -import java.util.Set; - import org.springframework.binding.convert.ConversionException; import org.springframework.binding.convert.ConversionExecutionException; import org.springframework.binding.convert.ConversionExecutor; @@ -113,7 +111,8 @@ public class FlowBuilderContextImpl implements FlowBuilderContext { * @return the flow builder conversion service */ protected ConversionService createConversionService() { - GenericConversionService service = new GenericConversionService(); + GenericConversionService service = new GenericConversionService(getFlowBuilderServices().getConversionService() + .getDelegateConversionService()); service.addConverter(new TextToTransitionCriteria(this)); service.addConverter(new TextToTargetStateResolver(this)); service.setParent(new ParentConversionServiceProxy()); @@ -145,10 +144,6 @@ public class FlowBuilderContextImpl implements FlowBuilderContext { return getFlowBuilderServices().getConversionService().getConversionExecutor(sourceClass, targetClass); } - public Set getConversionExecutors(Class sourceClass) { - return getFlowBuilderServices().getConversionService().getConversionExecutors(sourceClass); - } - public ConversionExecutor getConversionExecutor(String id, Class sourceClass, Class targetClass) throws ConversionExecutorNotFoundException { return getFlowBuilderServices().getConversionService().getConversionExecutor(id, sourceClass, targetClass); @@ -158,6 +153,10 @@ public class FlowBuilderContextImpl implements FlowBuilderContext { return getFlowBuilderServices().getConversionService().getClassForAlias(name); } + public org.springframework.core.convert.ConversionService getDelegateConversionService() { + return getFlowBuilderServices().getConversionService().getDelegateConversionService(); + } + } } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/expression/spel/WebFlowSpringELExpressionParser.java b/spring-webflow/src/main/java/org/springframework/webflow/expression/spel/WebFlowSpringELExpressionParser.java index 8e6e38c5..c0c08c07 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/expression/spel/WebFlowSpringELExpressionParser.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/expression/spel/WebFlowSpringELExpressionParser.java @@ -15,6 +15,7 @@ */ package org.springframework.webflow.expression.spel; +import org.springframework.binding.convert.ConversionService; import org.springframework.binding.expression.spel.SpringELExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; @@ -28,6 +29,15 @@ public class WebFlowSpringELExpressionParser extends SpringELExpressionParser { public WebFlowSpringELExpressionParser(SpelExpressionParser expressionParser) { super(expressionParser); + addDefaultPropertyAccessors(); + } + + public WebFlowSpringELExpressionParser(SpelExpressionParser expressionParser, ConversionService conversionService) { + super(expressionParser, conversionService); + addDefaultPropertyAccessors(); + } + + private void addDefaultPropertyAccessors() { addPropertyAccessor(new MessageSourcePropertyAccessor()); addPropertyAccessor(new FlowVariablePropertyAccessor()); addPropertyAccessor(new MapAdaptablePropertyAccessor()); diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/BindingModel.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/BindingModel.java index e20b24b9..24f32991 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/BindingModel.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/BindingModel.java @@ -22,8 +22,9 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.PropertyAccessorFactory; import org.springframework.beans.PropertyEditorRegistry; -import org.springframework.binding.convert.ConversionExecutor; import org.springframework.binding.convert.ConversionService; import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.ExpressionParser; @@ -35,6 +36,7 @@ import org.springframework.binding.message.Message; import org.springframework.binding.message.MessageContext; import org.springframework.binding.message.MessageCriteria; import org.springframework.binding.message.Severity; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.util.Assert; import org.springframework.validation.AbstractErrors; import org.springframework.validation.BindingResult; @@ -42,7 +44,6 @@ import org.springframework.validation.Errors; import org.springframework.validation.FieldError; import org.springframework.validation.ObjectError; import org.springframework.webflow.engine.builder.BinderConfiguration; -import org.springframework.webflow.engine.builder.BinderConfiguration.Binding; /** * Makes the properties of the "model" object available to Spring views during rendering. Also makes data binding (aka @@ -126,7 +127,7 @@ public class BindingModel extends AbstractErrors implements BindingResult { } public Class getFieldType(String field) { - return parseFieldExpression(fixedField(field)).getValueType(boundObject); + return parseFieldExpression(fixedField(field), false).getValueType(boundObject); } public Object getFieldValue(String field) { @@ -138,7 +139,7 @@ public class BindingModel extends AbstractErrors implements BindingResult { return fieldError.getOriginalValue(); } } - return getFormattedValue(parseFieldExpression(field)); + return getFormattedValue(field); } // not typically used by mvc views, but implemented to be on the safe side @@ -172,32 +173,14 @@ public class BindingModel extends AbstractErrors implements BindingResult { } public Object getRawFieldValue(String field) { - return parseFieldExpression(fixedField(field)).getValue(boundObject); + return parseFieldExpression(fixedField(field), false).getValue(boundObject); } public PropertyEditor findEditor(String field, Class valueType) { - if (conversionService != null) { - String converterId = null; - if (field != null) { - field = fixedField(field); - if (binderConfiguration != null) { - Binding binding = binderConfiguration.getBinding(field); - if (binding != null) { - converterId = binding.getConverter(); - } - } - if (valueType == null) { - valueType = parseFieldExpression(field).getValueType(boundObject); - } - } - if (valueType != null) { - return new ConversionExecutorPropertyEditor(conversionService, valueType, converterId); - } else { - return null; - } - } else { - return null; + if (field != null) { + field = fixedField(field); } + return findSpringConvertingPropertyEditor(field, valueType); } // never expected to be called by mvc views @@ -228,39 +211,69 @@ public class BindingModel extends AbstractErrors implements BindingResult { // internal helpers - private Expression parseFieldExpression(String field) { - return expressionParser.parseExpression(field, new FluentParserContext().evaluate(boundObject.getClass())); - } - - private Object getFormattedValue(Expression fieldExpression) { - ConversionExecutor converter = getConverter(fieldExpression); - if (converter != null) { - return converter.execute(fieldExpression.getValue(boundObject)); - } else { - return fieldExpression.getValue(boundObject); + private Expression parseFieldExpression(String field, boolean useResultTypeHint) { + FluentParserContext parserContext = new FluentParserContext().evaluate(boundObject.getClass()); + if (useResultTypeHint) { + parserContext.expectResult(String.class); } + return expressionParser.parseExpression(field, parserContext); } - private ConversionExecutor getConverter(Expression fieldExpression) { - if (conversionService != null) { - Class valueType = fieldExpression.getValueType(boundObject); - // special handling for array, collection, map types - // necessary as getFieldValue is called by form tags for non-formattable properties, too - // TODO - investigate how to improve this in Spring MVC - if (valueType == null || valueType.isArray() || Collection.class.isAssignableFrom(valueType) - || Map.class.isAssignableFrom(valueType)) { - return null; - } - if (binderConfiguration != null) { - Binding binding = binderConfiguration.getBinding(fieldExpression.getExpressionString()); - if (binding != null) { - String converterId = binding.getConverter(); - if (converterId != null) { - return conversionService.getConversionExecutor(converterId, valueType, String.class); - } + private Object getFormattedValue(String field) { + Expression fieldExpression = parseFieldExpression(field, true); + Class valueType = fieldExpression.getValueType(boundObject); + if (isCustomConverterConfigured(field) || avoidConversion(valueType)) { + fieldExpression = parseFieldExpression(fieldExpression.getExpressionString(), false); + } + Object value = fieldExpression.getValue(boundObject); + if ((value instanceof String) == false) { + if (avoidConversion(valueType) == false) { + PropertyEditor editor = findSpringConvertingPropertyEditor(field, valueType); + if (editor != null) { + editor.setValue(value); + value = editor.getAsText(); } } - return conversionService.getConversionExecutor(valueType, String.class); + } + return value; + } + + private boolean isCustomConverterConfigured(String field) { + if (binderConfiguration == null) { + return false; + } + return (binderConfiguration.getConverterId(field) != null); + } + + private boolean avoidConversion(Class valueType) { + // special handling for array, collection, map types + // necessary as getFieldValue is called by form tags for non-formattable properties, too + // TODO - investigate how to improve this in Spring MVC + if (valueType == null || valueType.isArray() || Collection.class.isAssignableFrom(valueType) + || Map.class.isAssignableFrom(valueType)) { + return true; + } + return false; + } + + private PropertyEditor findSpringConvertingPropertyEditor(String field, Class valueType) { + if (conversionService != null) { + String converterId = null; + if (field != null) { + if (binderConfiguration != null) { + converterId = binderConfiguration.getConverterId(field); + } + if (valueType == null) { + valueType = parseFieldExpression(field, false).getValueType(boundObject); + } + } + if (valueType != null) { + BeanWrapper accessor = PropertyAccessorFactory.forBeanPropertyAccess(boundObject); + TypeDescriptor typeDescriptor = accessor.getPropertyTypeDescriptor(field); + return new ConvertingPropertyEditorAdapter(conversionService, converterId, typeDescriptor); + } else { + return null; + } } else { return null; } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/ConversionExecutorPropertyEditor.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/ConvertingPropertyEditorAdapter.java similarity index 51% rename from spring-webflow/src/main/java/org/springframework/webflow/mvc/view/ConversionExecutorPropertyEditor.java rename to spring-webflow/src/main/java/org/springframework/webflow/mvc/view/ConvertingPropertyEditorAdapter.java index f4f2394b..6c274754 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/ConversionExecutorPropertyEditor.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/view/ConvertingPropertyEditorAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2004-2007 the original author or authors. + * Copyright 2004-2010 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. @@ -15,40 +15,63 @@ */ package org.springframework.webflow.mvc.view; +import java.beans.PropertyEditor; import java.beans.PropertyEditorSupport; import org.springframework.binding.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -class ConversionExecutorPropertyEditor extends PropertyEditorSupport { +/** + *

+ * A {@link PropertyEditor} that delegates to a Spring ConversionService unless a converterId is provided. When a + * converterId is provided, conversion will be delegated to the Spring Binding ConversionService instead as Spring's + * type conversion system does not support named converters. + *

+ * + * @author Rossen Stoyanchev + */ +class ConvertingPropertyEditorAdapter extends PropertyEditorSupport { private ConversionService conversionService; - private Class fieldType; + private TypeDescriptor fieldType; private String converterId; - public ConversionExecutorPropertyEditor(ConversionService conversionService, Class fieldType, String converterId) { + private boolean canConvertToString; + + public ConvertingPropertyEditorAdapter(ConversionService conversionService, String converterId, + TypeDescriptor fieldType) { + Assert.notNull(conversionService, "A ConversionService instance is required."); Assert.notNull(fieldType, "The field type is required"); this.conversionService = conversionService; this.fieldType = fieldType; this.converterId = converterId; + this.canConvertToString = conversionService.getDelegateConversionService().canConvert(this.fieldType, + TypeDescriptor.valueOf(String.class)); } public String getAsText() { if (StringUtils.hasText(converterId)) { return (String) conversionService.executeConversion(converterId, getValue(), String.class); } else { - return (String) conversionService.executeConversion(getValue(), String.class); + if (canConvertToString) { + return (String) conversionService.getDelegateConversionService().convert(getValue(), fieldType, + TypeDescriptor.valueOf(String.class)); + } else { + return null; + } } } public void setAsText(String text) throws IllegalArgumentException { if (StringUtils.hasText(converterId)) { - setValue(conversionService.executeConversion(converterId, text, fieldType)); + setValue(conversionService.executeConversion(converterId, text, fieldType.getType())); } else { - setValue(conversionService.executeConversion(text, fieldType)); + setValue(conversionService.getDelegateConversionService().convert(text, + TypeDescriptor.valueOf(String.class), fieldType)); } } } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/test/TestFlowBuilderServicesFactory.java b/spring-webflow/src/main/java/org/springframework/webflow/test/TestFlowBuilderServicesFactory.java index ca9ec363..c00b502c 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/test/TestFlowBuilderServicesFactory.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/test/TestFlowBuilderServicesFactory.java @@ -1,5 +1,6 @@ package org.springframework.webflow.test; +import org.springframework.binding.convert.ConversionService; import org.springframework.binding.convert.service.DefaultConversionService; import org.springframework.context.support.StaticApplicationContext; import org.springframework.expression.spel.standard.SpelExpressionParser; @@ -15,10 +16,15 @@ public class TestFlowBuilderServicesFactory { } public static FlowBuilderServices getServices() { - FlowBuilderServices services = new FlowBuilderServices(); + FlowBuilderServices services = new FlowBuilderServices() { + // The SpEL parser must use the currently configured conversion service. + public void setConversionService(ConversionService conversionService) { + super.setConversionService(conversionService); + setExpressionParser(new WebFlowSpringELExpressionParser(new SpelExpressionParser(), conversionService)); + } + }; services.setViewFactoryCreator(new MockViewFactoryCreator()); services.setConversionService(new DefaultConversionService()); - services.setExpressionParser(new WebFlowSpringELExpressionParser(new SpelExpressionParser())); services.setApplicationContext(createTestApplicationContext()); return services; } @@ -28,4 +34,5 @@ public class TestFlowBuilderServicesFactory { context.refresh(); return context; } + } \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/action/ActionResultExposerTests.java b/spring-webflow/src/test/java/org/springframework/webflow/action/ActionResultExposerTests.java deleted file mode 100644 index 1428e3af..00000000 --- a/spring-webflow/src/test/java/org/springframework/webflow/action/ActionResultExposerTests.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.springframework.webflow.action; - -import junit.framework.TestCase; - -import org.springframework.binding.convert.service.DefaultConversionService; -import org.springframework.binding.expression.support.StaticExpression; -import org.springframework.webflow.test.MockRequestContext; - -public class ActionResultExposerTests extends TestCase { - - public void testEvaluateExpressionResult() throws Exception { - StaticExpression resultExpression = new StaticExpression(""); - ActionResultExposer exposer = new ActionResultExposer(resultExpression, null, null); - MockRequestContext context = new MockRequestContext(); - exposer.exposeResult("foo", context); - assertEquals("foo", resultExpression.getValue(null)); - } - - public void testEvaluateExpressionNullResult() throws Exception { - StaticExpression resultExpression = new StaticExpression(""); - ActionResultExposer exposer = new ActionResultExposer(resultExpression, null, null); - MockRequestContext context = new MockRequestContext(); - exposer.exposeResult(null, context); - assertEquals(null, resultExpression.getValue(null)); - } - - public void testEvaluateExpressionResultExposerWithTypeConversion() throws Exception { - StaticExpression resultExpression = new StaticExpression(""); - ActionResultExposer exposer = new ActionResultExposer(resultExpression, Integer.class, - new DefaultConversionService()); - MockRequestContext context = new MockRequestContext(); - exposer.exposeResult("3", context); - assertEquals(new Integer(3), resultExpression.getValue(null)); - } - - public void testEvaluateExpressionResultExposerWithTypeConversionForgotArgument() throws Exception { - StaticExpression resultExpression = new StaticExpression(""); - try { - new ActionResultExposer(resultExpression, Integer.class, null); - fail("should have failed iae"); - } catch (IllegalArgumentException e) { - - } - } - -} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/action/EvaluateActionTests.java b/spring-webflow/src/test/java/org/springframework/webflow/action/EvaluateActionTests.java index 52daea80..640845a7 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/action/EvaluateActionTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/action/EvaluateActionTests.java @@ -50,8 +50,7 @@ public class EvaluateActionTests extends TestCase { public void testEvaluateExpressionResultExposer() throws Exception { StaticExpression resultExpression = new StaticExpression(""); - EvaluateAction action = new EvaluateAction(new StaticExpression("bar"), new ActionResultExposer( - resultExpression, null, null)); + EvaluateAction action = new EvaluateAction(new StaticExpression("bar"), resultExpression); MockRequestContext context = new MockRequestContext(); Event result = action.execute(context); assertEquals("bar", result.getId()); diff --git a/spring-webflow/src/test/java/org/springframework/webflow/action/SetActionTests.java b/spring-webflow/src/test/java/org/springframework/webflow/action/SetActionTests.java index ee6b13e9..62ae585c 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/action/SetActionTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/action/SetActionTests.java @@ -2,28 +2,19 @@ package org.springframework.webflow.action; import junit.framework.TestCase; -import org.springframework.binding.convert.service.DefaultConversionService; import org.springframework.binding.expression.support.StaticExpression; import org.springframework.webflow.execution.Event; import org.springframework.webflow.test.MockRequestContext; public class SetActionTests extends TestCase { + public void testSetAction() throws Exception { StaticExpression name = new StaticExpression(""); - SetAction action = new SetAction(name, new StaticExpression("bar"), null, null); + SetAction action = new SetAction(name, new StaticExpression("bar")); MockRequestContext context = new MockRequestContext(); Event result = action.execute(context); assertEquals("success", result.getId()); assertEquals("bar", name.getValue(null)); } - public void testSetActionWithTypeConversion() throws Exception { - StaticExpression name = new StaticExpression(""); - SetAction action = new SetAction(name, new StaticExpression("3"), Integer.class, new DefaultConversionService()); - MockRequestContext context = new MockRequestContext(); - Event result = action.execute(context); - assertEquals("success", result.getId()); - assertEquals(new Integer(3), name.getValue(null)); - } - } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/config/FlowBuilderServicesBeanDefinitionParserTests.java b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowBuilderServicesBeanDefinitionParserTests.java index 7fc343d9..0841e7c4 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/config/FlowBuilderServicesBeanDefinitionParserTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/config/FlowBuilderServicesBeanDefinitionParserTests.java @@ -52,7 +52,7 @@ public class FlowBuilderServicesBeanDefinitionParserTests extends TestCase { assertNotNull(builderServices); assertTrue(builderServices.getConversionService() instanceof TestConversionService); assertTrue(builderServices.getExpressionParser() instanceof SpringELExpressionParser); - assertNotNull(((SpringELExpressionParser) builderServices.getExpressionParser()).getConversionService()); + assertTrue(((SpringELExpressionParser) builderServices.getExpressionParser()).getConversionService() instanceof TestConversionService); assertTrue(builderServices.getViewFactoryCreator() instanceof MvcViewFactoryCreator); assertFalse(builderServices.getDevelopment()); } @@ -98,6 +98,10 @@ public class FlowBuilderServicesBeanDefinitionParserTests extends TestCase { throw new UnsupportedOperationException("Auto-generated method stub"); } + public org.springframework.core.convert.ConversionService getDelegateConversionService() { + throw new UnsupportedOperationException("Auto-generated method stub"); + } + } } \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilderTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilderTests.java index 458c203b..f9f63cc0 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilderTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/model/FlowModelFlowBuilderTests.java @@ -20,6 +20,7 @@ import org.springframework.webflow.engine.builder.FlowBuilderException; import org.springframework.webflow.engine.impl.FlowExecutionImplFactory; import org.springframework.webflow.engine.model.AttributeModel; import org.springframework.webflow.engine.model.EndStateModel; +import org.springframework.webflow.engine.model.EvaluateModel; import org.springframework.webflow.engine.model.ExceptionHandlerModel; import org.springframework.webflow.engine.model.FlowModel; import org.springframework.webflow.engine.model.InputModel; @@ -27,6 +28,7 @@ import org.springframework.webflow.engine.model.Model; import org.springframework.webflow.engine.model.OutputModel; import org.springframework.webflow.engine.model.PersistenceContextModel; import org.springframework.webflow.engine.model.SecuredModel; +import org.springframework.webflow.engine.model.SetModel; import org.springframework.webflow.engine.model.TransitionModel; import org.springframework.webflow.engine.model.VarModel; import org.springframework.webflow.engine.model.ViewStateModel; @@ -36,6 +38,7 @@ import org.springframework.webflow.engine.model.builder.xml.XmlFlowModelBuilderT import org.springframework.webflow.engine.model.registry.FlowModelHolder; import org.springframework.webflow.engine.model.registry.FlowModelRegistryImpl; import org.springframework.webflow.engine.support.ActionExecutingViewFactory; +import org.springframework.webflow.execution.AnnotatedAction; import org.springframework.webflow.execution.FlowExecution; import org.springframework.webflow.execution.FlowExecutionException; import org.springframework.webflow.execution.FlowExecutionOutcome; @@ -43,6 +46,7 @@ import org.springframework.webflow.execution.ViewFactory; import org.springframework.webflow.security.SecurityRule; import org.springframework.webflow.test.MockExternalContext; import org.springframework.webflow.test.MockFlowBuilderContext; +import org.springframework.webflow.test.MockRequestContext; public class FlowModelFlowBuilderTests extends TestCase { private FlowModel model; @@ -306,6 +310,77 @@ public class FlowModelFlowBuilderTests extends TestCase { assertEquals(1, flow.getExceptionHandlerSet().size()); } + public void testSetActionWithResultType() throws Exception { + SetModel setModel = new SetModel("flowScope.stringArray", "intArray"); + setModel.setType("java.lang.String[]"); + model.setOnStartActions(singleList(setModel)); + model.setStates(singleList(new ViewStateModel("view"))); + Flow flow = getFlow(model); + AnnotatedAction action = (AnnotatedAction) flow.getStartActionList().get(0); + MockRequestContext context = new MockRequestContext(flow); + context.getFlowScope().put("intArray", new int[] { 1, 2 }); + action.execute(context); + String[] expected = (String[]) context.getFlowScope().get("stringArray"); + assertEquals("1", expected[0]); + assertEquals("2", expected[1]); + } + + public void testSetActionWithImplicitTypeConversion() throws Exception { + SetModel setModel = new SetModel("testBean.stringArray", "intArray"); + model.setOnStartActions(singleList(setModel)); + ViewStateModel state = new ViewStateModel("view"); + model.setStates(singleList(state)); + Flow flow = getFlow(model); + AnnotatedAction action = (AnnotatedAction) flow.getStartActionList().get(0); + MockRequestContext context = new MockRequestContext(flow); + context.getFlowScope().put("testBean", new TestBean()); + context.getFlowScope().put("intArray", new int[] { 1, 2 }); + action.execute(context); + TestBean expected = (TestBean) context.getFlowScope().get("testBean"); + assertEquals("1", expected.stringArray[0]); + assertEquals("2", expected.stringArray[1]); + } + + public void testEvaluateActionWithResultType() throws Exception { + EvaluateModel evaluateModel = new EvaluateModel("testBean.getIntegers()"); + evaluateModel.setResult("flowScope.stringArray"); + evaluateModel.setResultType("java.lang.String[]"); + model.setOnStartActions(singleList(evaluateModel)); + model.setStates(singleList(new ViewStateModel("view"))); + Flow flow = getFlow(model); + AnnotatedAction action = (AnnotatedAction) flow.getStartActionList().get(0); + MockRequestContext context = new MockRequestContext(flow); + context.getFlowScope().put("testBean", new TestBean()); + action.execute(context); + String[] expected = (String[]) context.getFlowScope().get("stringArray"); + assertEquals("1", expected[0]); + assertEquals("2", expected[1]); + } + + public void testEvaluateActionWithELExpression() throws Exception { + EvaluateModel evaluateModel = new EvaluateModel("testBean.getIntegers()"); + evaluateModel.setResult("flowScope.stringArray"); + evaluateModel.setResultType("java.lang.String[]"); + model.setOnStartActions(singleList(evaluateModel)); + model.setStates(singleList(new ViewStateModel("view"))); + Flow flow = getFlow(model); + AnnotatedAction action = (AnnotatedAction) flow.getStartActionList().get(0); + MockRequestContext context = new MockRequestContext(flow); + context.getFlowScope().put("testBean", new TestBean()); + action.execute(context); + String[] expected = (String[]) context.getFlowScope().get("stringArray"); + assertEquals("1", expected[0]); + assertEquals("2", expected[1]); + } + + private static class TestBean { + public String[] stringArray; + + public int[] getIntegers() { + return new int[] { 1, 2 }; + } + } + private Flow getFlow(FlowModel model) { FlowModelHolder holder = new StaticFlowModelHolder(model); FlowModelFlowBuilder builder = new FlowModelFlowBuilder(holder); diff --git a/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/AbstractBindingModelTests.java b/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/AbstractBindingModelTests.java index 2a0b5867..877b641f 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/AbstractBindingModelTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/AbstractBindingModelTests.java @@ -66,12 +66,6 @@ public abstract class AbstractBindingModelTests extends TestCase { assertEquals(new Integer(3), model.getRawFieldValue("datum2")); } - public void testGetFieldValueNonStringNoConversionService() { - model = new BindingModel("testBean", testBean, getExpressionParser(), null, messages); - testBean.datum2 = 3; - assertEquals(new Integer(3), model.getFieldValue("datum2")); - } - public void testGetFieldValueConvertedWithCustomConverter() { testBean.datum2 = 3; conversionService.addConverter("customConverter", new StringToObject(Integer.class) { diff --git a/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/MvcViewTests.java b/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/MvcViewTests.java index ea87f8ea..cc2a2fd4 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/MvcViewTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/MvcViewTests.java @@ -6,7 +6,6 @@ import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.security.Principal; -import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.List; @@ -19,14 +18,13 @@ import junit.framework.TestCase; import org.springframework.binding.convert.converters.StringToDate; import org.springframework.binding.convert.service.DefaultConversionService; +import org.springframework.binding.convert.service.GenericConversionService; import org.springframework.binding.expression.EvaluationException; import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.spel.SpringELExpressionParser; import org.springframework.binding.expression.support.StaticExpression; import org.springframework.binding.validation.ValidationContext; import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.format.datetime.DateFormatter; -import org.springframework.format.support.FormattingConversionService; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockMultipartFile; @@ -393,8 +391,8 @@ public class MvcViewTests extends TestCase { assertEquals(context2.getFlowScope().get("bindBean"), model.get("bindBean")); BindingModel bm = (BindingModel) model.get(BindingResult.MODEL_KEY_PREFIX + "bindBean"); assertNotNull(bm); - assertEquals(new Integer(3), bm.getFieldValue("integerProperty")); - assertEquals(new SimpleDateFormat("MM-dd-yyyy").parse("01-01-2008"), bm.getFieldValue("dateProperty")); + assertEquals("3", bm.getFieldValue("integerProperty")); + assertEquals("2008-01-01", bm.getFieldValue("dateProperty")); } private Object saveAndRestoreViewActionState(Object viewActionState) throws Exception { @@ -630,11 +628,12 @@ public class MvcViewTests extends TestCase { } private SpringELExpressionParser createExpressionParser() { - SpringELExpressionParser expressionParser = new WebFlowSpringELExpressionParser(new SpelExpressionParser()); - FormattingConversionService conversionService = (FormattingConversionService) expressionParser - .getConversionService(); - conversionService.addFormatterForFieldType(Date.class, new DateFormatter("yyyy-MM-dd")); - return expressionParser; + StringToDate c = new StringToDate(); + c.setPattern("yyyy-MM-dd"); + SpringELExpressionParser parser = new WebFlowSpringELExpressionParser(new SpelExpressionParser()); + GenericConversionService cs = (GenericConversionService) parser.getConversionService(); + cs.addConverter(c); + return parser; } private class MockMvcView extends AbstractMvcView { diff --git a/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/SpringBeanBindingModelTests.java b/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/SpringBeanBindingModelTests.java index 1897b551..10e71bbc 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/SpringBeanBindingModelTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/mvc/view/SpringBeanBindingModelTests.java @@ -16,4 +16,13 @@ public class SpringBeanBindingModelTests extends AbstractBindingModelTests { PropertyEditor editor = model.findEditor("emptyMap['foo']", null); assertNull(editor); } + + // BeanWrapper-based EL does not accept result type hints. + // Hence it requires a conversion service. + public void testGetFieldValueNonStringNoConversionService() { + model = new BindingModel("testBean", testBean, getExpressionParser(), null, messages); + testBean.datum2 = 3; + assertEquals(new Integer(3), model.getFieldValue("datum2")); + } + }