bug fixes related to binding and type conversion

This commit is contained in:
Keith Donald
2008-07-03 17:23:42 +00:00
parent f20578be56
commit 29cb51240e
25 changed files with 242 additions and 48 deletions

View File

@@ -19,8 +19,7 @@ package org.springframework.binding.convert;
* A service interface for retrieving type conversion executors. The returned command objects are thread-safe and may be
* safely cached for use by client code.
*
* Type converters convert from one type to another. They are more generic than formatters, which convert from string to
* object and back.
* Type converters convert from one type to another.
*
* @author Keith Donald
*/
@@ -39,13 +38,19 @@ public interface ConversionService {
public ConversionExecutor getConversionExecutor(Class sourceClass, Class targetClass)
throws ConversionExecutorNotFoundException;
/**
* Return all conversion executors capable of converting <i>from</i> the provided <code>sourceClass</code>. For
* example, <code>ConversionExecutor[] getConversionExecutor(String.class)</code> would return all converters that
* convert from String to some other Object.
* @param sourceClass the source class converting from
* @return the conversion executors that can convert from that source class
*/
public ConversionExecutor[] getConversionExecutors(Class sourceClass);
/**
* Lookup a class by it alias. For example, <code>long</code> for <code>java.lang.Long</code>
* @param alias the class alias
* @return the class, or <code>null</code> if no alias exists
*/
public Class getClassForAlias(String alias);
public ConversionExecutor[] getConversionExecutors(Class sourceClass);
}

View File

@@ -1,3 +1,18 @@
/*
* 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.binding.convert.converters;
import java.lang.reflect.Array;
@@ -5,10 +20,21 @@ import java.lang.reflect.Array;
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.binding.convert.ConversionService;
/**
* Special one-way converter that converts from a source array to a target array. Supports type conversion of the
* individual array elements; for example, the ability to convert a String[] to an Integer[]. Mainly used internally by
* {@link ConversionService} implementations.
*
* @author Keith Donald
*/
public class ArrayToArray implements Converter {
private ConversionService conversionService;
/**
* Creates a new array-to-array converter.
* @param conversionService the service to use to lookup conversion executors for individual array elements
*/
public ArrayToArray(ConversionService conversionService) {
this.conversionService = conversionService;
}

View File

@@ -1,3 +1,18 @@
/*
* 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.binding.convert.converters;
import java.lang.reflect.Array;
@@ -16,6 +31,19 @@ import org.springframework.binding.convert.ConversionService;
import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.JdkVersion;
/**
* Special one-way converter that converts from a source array to a target collection. Supports the selection of an
* "approximate" collection implementation when a target collection interface such as <code>List.class</code> is
* specified. Supports type conversion of array elements when a concrete parameterized collection class is provided,
* such as <code>IntegerList<Integer>.class</code>.
*
* Note that type erasure prevents arbitrary access to generic collection element type information at runtime,
* preventing the ability to convert elements for collections declared as properties.
*
* Mainly used internally by {@link ConversionService} implementations.
*
* @author Keith Donald
*/
public class ArrayToCollection implements TwoWayConverter {
private ConversionService conversionService;

View File

@@ -1,3 +1,18 @@
/*
* 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.binding.convert.converters;
import java.lang.reflect.Array;

View File

@@ -1,3 +1,18 @@
/*
* 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.binding.convert.converters;
import java.beans.PropertyEditor;

View File

@@ -1,3 +1,18 @@
/*
* 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.binding.convert.converters;
public class ReverseConverter implements Converter {

View File

@@ -1,3 +1,18 @@
/*
* 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.binding.convert.converters;
public class StringToCharacter extends StringToObject {

View File

@@ -1,3 +1,18 @@
/*
* 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.binding.convert.converters;
public abstract class StringToObject implements TwoWayConverter {

View File

@@ -20,14 +20,11 @@ import java.math.BigInteger;
import java.util.Date;
import java.util.Locale;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.binding.convert.ConversionService;
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.StringToClass;
import org.springframework.binding.convert.converters.StringToDate;
import org.springframework.binding.convert.converters.StringToDouble;
import org.springframework.binding.convert.converters.StringToFloat;
@@ -37,7 +34,6 @@ 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.enums.LabeledEnum;
import org.springframework.util.ClassUtils;
/**
* Default, local implementation of a conversion service. Will automatically register <i>from string</i> converters for
@@ -45,14 +41,7 @@ import org.springframework.util.ClassUtils;
*
* @author Keith Donald
*/
public class DefaultConversionService extends GenericConversionService implements BeanClassLoaderAware {
/**
* A singleton shared instance. Should never be modified.
*/
private static DefaultConversionService SHARED_INSTANCE;
private ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
public class DefaultConversionService extends GenericConversionService {
/**
* Creates a new default conversion service, installing the default converters.
@@ -76,7 +65,6 @@ public class DefaultConversionService extends GenericConversionService implement
addConverter(new StringToDouble());
addConverter(new StringToBigInteger());
addConverter(new StringToBigDecimal());
addConverter(new StringToClass(classLoader));
addConverter(new StringToLocale());
addConverter(new StringToDate());
addConverter(new StringToLabeledEnum());
@@ -94,23 +82,9 @@ public class DefaultConversionService extends GenericConversionService implement
addAlias("double", Double.class);
addAlias("bigInteger", BigInteger.class);
addAlias("bigDecimal", BigDecimal.class);
addAlias("class", Class.class);
addAlias("locale", Locale.class);
addAlias("date", Date.class);
addAlias("labeledEnum", LabeledEnum.class);
}
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* Returns the shared {@link DefaultConversionService} instance.
*/
public synchronized static ConversionService getSharedInstance() {
if (SHARED_INSTANCE == null) {
SHARED_INSTANCE = new DefaultConversionService();
}
return SHARED_INSTANCE;
}
}

View File

@@ -1,3 +1,18 @@
/*
* 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.binding.expression.beanwrapper;
import java.beans.PropertyEditorSupport;
@@ -12,12 +27,34 @@ import org.springframework.binding.expression.EvaluationException;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.PropertyNotFoundException;
/**
* An expression that delegates to a {@link BeanWrapperImpl bean wrapper} to evaluate or set a property of a context.
*
* Also supports the configuration of a {@link ConversionService} to allow StringToObject type conversion to occur as
* part of setting a property. The StringToObject ConversionExecutors are automatically adapted and registered as
* PropertyEditors.
*
* Mainly exists to take advantage of BeanWrapper's unique property access features as an Expression implementation,
* notably the ability to infer types of generic collections and maps and perform type coersion on collection elements
* when setting values.
*
* Note that Spring's BeanWrapper is not a full-blown EL implementation: it only supports property access, and does not
* support method invocation, arithmetic operations, or logic operations.
*
* @author Keith Donald
*/
public class BeanWrapperExpression implements Expression {
private String expression;
private ConversionService conversionService;
/**
* Creates a new bean wrapper expression.
* @param expression the property expression string
* @param conversionService the conversion service containing converters to use as PropertyEditors for type
* conversion
*/
public BeanWrapperExpression(String expression, ConversionService conversionService) {
this.expression = expression;
this.conversionService = conversionService;
@@ -81,7 +118,7 @@ public class BeanWrapperExpression implements Expression {
return expression;
}
public static class PropertyEditorConverter extends PropertyEditorSupport {
private static class PropertyEditorConverter extends PropertyEditorSupport {
private ConversionExecutor converter;

View File

@@ -15,6 +15,9 @@
*/
package org.springframework.binding.expression.beanwrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.propertyeditors.PropertiesEditor;
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ParserContext;
@@ -22,7 +25,7 @@ import org.springframework.binding.expression.ParserException;
import org.springframework.binding.expression.support.AbstractExpressionParser;
/**
* An expression parser that parses Ognl expressions.
* An expression parser that parses BeanWrapper property expressions.
*
* @author Keith Donald
*/
@@ -30,10 +33,22 @@ public class BeanWrapperExpressionParser extends AbstractExpressionParser {
private ConversionService conversionService;
/**
* The conversion service to use to obtain {@link ConversionExecutor conversion executors} that will be adapted to
* {@link PropertiesEditor property editors} for use during a
* {@link BeanWrapperImpl#setPropertyValue(String, Object) set value} call.
* @return the conversion service
*/
public ConversionService getConversionService() {
return conversionService;
}
/**
* Sets the conversion service to use to obtain {@link ConversionExecutor conversion executors} that will be adapted
* to {@link PropertiesEditor property editors} for use during a
* {@link BeanWrapperImpl#setPropertyValue(String, Object) set value} call.
* @param conversionService the conversion service
*/
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}

View File

@@ -44,7 +44,8 @@ public class DefaultExpressionFactoryUtils {
public static ExpressionFactory createExpressionFactory() throws IllegalStateException {
Class expressionFactoryClass;
try {
expressionFactoryClass = ClassUtils.forName(getDefaultExpressionFactoryClassName());
expressionFactoryClass = ClassUtils.forName(getDefaultExpressionFactoryClassName(),
DefaultExpressionFactoryUtils.class.getClassLoader());
} catch (ClassNotFoundException e) {
IllegalStateException ise = new IllegalStateException(
"The default ExpressionFactory class '"

View File

@@ -17,6 +17,8 @@ package org.springframework.binding.expression.ognl;
import ognl.Ognl;
import ognl.OgnlException;
import ognl.OgnlRuntime;
import ognl.PropertyAccessor;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ParserContext;
@@ -29,6 +31,16 @@ import org.springframework.binding.expression.support.AbstractExpressionParser;
* @author Keith Donald
*/
public class OgnlExpressionParser extends AbstractExpressionParser {
/**
* Add a property access strategy for the given class.
* @param clazz the class that contains properties needing access
* @param propertyAccessor the property access strategy
*/
public void addPropertyAccessor(Class clazz, PropertyAccessor propertyAccessor) {
OgnlRuntime.setPropertyAccessor(clazz, propertyAccessor);
}
protected Expression doParseExpression(String expressionString, ParserContext context) throws ParserException {
try {
return new OgnlExpression(Ognl.parseExpression(expressionString), parseVariableExpressions(context

View File

@@ -39,7 +39,7 @@ public class MethodInvoker {
/**
* Conversion service for converting arguments to the necessary type if required.
*/
private ConversionService conversionService = DefaultConversionService.getSharedInstance();
private ConversionService conversionService = new DefaultConversionService();
/**
* A cache of invoked bean methods, keyed weakly.

View File

@@ -76,6 +76,10 @@ public class FacesFlowBuilderServicesBeanDefinitionParserTests extends TestCase
throw new UnsupportedOperationException("Auto-generated method stub");
}
public ConversionExecutor[] getConversionExecutors(Class sourceClass) {
throw new UnsupportedOperationException("Auto-generated method stub");
}
public Class getClassForAlias(String name) throws ConversionExecutionException {
throw new UnsupportedOperationException("Auto-generated method stub");
}

View File

@@ -48,7 +48,7 @@ class FlowBuilderServicesBeanDefinitionParser extends AbstractSingleBeanDefiniti
if (StringUtils.hasText(conversionService)) {
definitionBuilder.addPropertyReference("conversionService", conversionService);
} else {
definitionBuilder.addPropertyValue("conversionService", DefaultConversionService.getSharedInstance());
definitionBuilder.addPropertyValue("conversionService", new DefaultConversionService());
}
}

View File

@@ -72,7 +72,7 @@ class FlowExecutorFactoryBean implements FactoryBean, ApplicationContextAware, B
private FlowExecutionListenerLoader flowExecutionListenerLoader;
private ConversionService conversionService = DefaultConversionService.getSharedInstance();
private ConversionService conversionService;
private FlowExecutor flowExecutor;
@@ -137,6 +137,9 @@ class FlowExecutorFactoryBean implements FactoryBean, ApplicationContextAware, B
public void afterPropertiesSet() throws Exception {
Assert.notNull(flowDefinitionLocator, "The flow definition locator property is required");
if (conversionService == null) {
conversionService = new DefaultConversionService();
}
MutableAttributeMap executionAttributes = createFlowExecutionAttributes();
FlowExecutionImplFactory executionFactory = createFlowExecutionFactory(executionAttributes);
DefaultFlowExecutionRepository executionRepository = createFlowExecutionRepository(executionFactory);

View File

@@ -126,7 +126,7 @@ class FlowRegistryBeanDefinitionParser extends AbstractSingleBeanDefinitionParse
private BeanDefinition createDefaultFlowBuilderServices(ParserContext context) {
BeanDefinitionBuilder defaultBuilder = BeanDefinitionBuilder.genericBeanDefinition(FlowBuilderServices.class);
defaultBuilder.addPropertyValue("conversionService", DefaultConversionService.getSharedInstance());
defaultBuilder.addPropertyValue("conversionService", new DefaultConversionService());
defaultBuilder.addPropertyValue("expressionParser", DefaultExpressionParserFactory.getExpressionParser());
defaultBuilder.addPropertyValue("viewFactoryCreator", BeanDefinitionBuilder.genericBeanDefinition(
MvcViewFactoryCreator.class).getBeanDefinition());

View File

@@ -42,6 +42,8 @@ import org.springframework.web.multipart.MultipartFile;
*/
public class LocalParameterMap implements ParameterMap, Serializable {
private static final DefaultConversionService DEFAULT_CONVERSION_SERVICE = new DefaultConversionService();
/**
* The backing map storing the parameters.
*/
@@ -65,7 +67,7 @@ public class LocalParameterMap implements ParameterMap, Serializable {
* @param parameters the contents of this parameter map
*/
public LocalParameterMap(Map parameters) {
this(parameters, DefaultConversionService.getSharedInstance());
this(parameters, DEFAULT_CONVERSION_SERVICE);
}
/**
@@ -192,7 +194,8 @@ public class LocalParameterMap implements ParameterMap, Serializable {
return (Number) get(parameterName, targetType);
}
public Number getNumber(String parameterName, Class targetType, Number defaultValue) throws ConversionExecutionException {
public Number getNumber(String parameterName, Class targetType, Number defaultValue)
throws ConversionExecutionException {
assertAssignableTo(Number.class, targetType);
return (Number) get(parameterName, targetType, defaultValue);
}
@@ -211,7 +214,8 @@ public class LocalParameterMap implements ParameterMap, Serializable {
return (Integer) get(parameterName, Integer.class, defaultValue);
}
public Integer getRequiredInteger(String parameterName) throws IllegalArgumentException, ConversionExecutionException {
public Integer getRequiredInteger(String parameterName) throws IllegalArgumentException,
ConversionExecutionException {
return (Integer) getRequired(parameterName, Integer.class);
}
@@ -235,7 +239,8 @@ public class LocalParameterMap implements ParameterMap, Serializable {
return (Boolean) get(parameterName, Boolean.class, defaultValue);
}
public Boolean getRequiredBoolean(String parameterName) throws IllegalArgumentException, ConversionExecutionException {
public Boolean getRequiredBoolean(String parameterName) throws IllegalArgumentException,
ConversionExecutionException {
return (Boolean) getRequired(parameterName, Boolean.class);
}
@@ -305,7 +310,7 @@ public class LocalParameterMap implements ParameterMap, Serializable {
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
parameterAccessor = new MapAccessor(parameters);
conversionService = DefaultConversionService.getSharedInstance();
conversionService = DEFAULT_CONVERSION_SERVICE;
}
public String toString() {

View File

@@ -124,6 +124,10 @@ public class FlowBuilderContextImpl implements FlowBuilderContext {
return getFlowBuilderServices().getConversionService().getConversionExecutor(sourceClass, targetClass);
}
public ConversionExecutor[] getConversionExecutors(Class sourceClass) {
return getFlowBuilderServices().getConversionService().getConversionExecutors(sourceClass);
}
public Class getClassForAlias(String name) {
return getFlowBuilderServices().getConversionService().getClassForAlias(name);
}

View File

@@ -94,7 +94,7 @@ public final class DefaultExpressionParserFactory {
return new WebFlowELExpressionParser(elFactory);
} catch (Exception e) {
try {
ClassUtils.forName("ognl.Ognl");
ClassUtils.forName("ognl.Ognl", DefaultExpressionParserFactory.class.getClassLoader());
return new WebFlowOgnlExpressionParser();
} catch (ClassNotFoundException ex) {
IllegalStateException ise = new IllegalStateException(

View File

@@ -28,7 +28,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.convert.service.DefaultConversionService;
import org.springframework.binding.expression.EvaluationException;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
@@ -77,7 +76,7 @@ public abstract class AbstractMvcView implements View {
private ExpressionParser expressionParser = DefaultExpressionParserFactory.getExpressionParser();
private ConversionService conversionService = DefaultConversionService.getSharedInstance();
private ConversionService conversionService;
private MappingResults mappingResults;

View File

@@ -16,7 +16,7 @@ public class TestFlowBuilderServicesFactory {
public static FlowBuilderServices getServices() {
FlowBuilderServices services = new FlowBuilderServices();
services.setViewFactoryCreator(new MockViewFactoryCreator());
services.setConversionService(DefaultConversionService.getSharedInstance());
services.setConversionService(new DefaultConversionService());
services.setExpressionParser(DefaultExpressionParserFactory.getExpressionParser());
services.setApplicationContext(createTestApplicationContext());
return services;

View File

@@ -59,6 +59,10 @@ public class FlowBuilderServicesBeanDefinitionParserTests extends TestCase {
throw new UnsupportedOperationException("Auto-generated method stub");
}
public ConversionExecutor[] getConversionExecutors(Class sourceClass) {
throw new UnsupportedOperationException("Auto-generated method stub");
}
public Class getClassForAlias(String alias) throws ConversionExecutionException {
throw new UnsupportedOperationException("Auto-generated method stub");
}

View File

@@ -12,6 +12,7 @@ import javax.servlet.http.HttpServletResponse;
import junit.framework.TestCase;
import org.springframework.binding.convert.service.DefaultConversionService;
import org.springframework.binding.expression.support.StaticExpression;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
@@ -106,6 +107,7 @@ public class MvcViewTests extends TestCase {
context.getMockFlowExecutionContext().setKey(new MockFlowExecutionKey("c1v1"));
org.springframework.web.servlet.View mvcView = new MockView();
AbstractMvcView view = new MockMvcView(mvcView, context);
view.setConversionService(new DefaultConversionService());
view.render();
assertEquals(context.getFlowScope().get("bindBean"), model.get("bindBean"));
BindingModel bm = (BindingModel) model.get(BindingResult.MODEL_KEY_PREFIX + "bindBean");