simplified conversion service

mvc view postback
This commit is contained in:
Keith Donald
2008-03-20 05:55:43 +00:00
parent 636e0cf0ca
commit 0f333805d9
19 changed files with 426 additions and 795 deletions

View File

@@ -38,32 +38,4 @@ public interface ConversionService {
*/
public ConversionExecutor getConversionExecutor(Class sourceClass, Class targetClass) throws ConversionException;
/**
* Return a conversion executor command object capable of converting source objects of the specified
* <code>sourceClass</code> to target objects of the type associated with the specified alias.
* @param sourceClass the sourceClass
* @param targetAlias the target alias
* @return the conversion executor, or null if the alias cannot be found
* @throws ConversionException an exception occured retrieving a converter for the source-to-target pair
*/
public ConversionExecutor getConversionExecutorByTargetAlias(Class sourceClass, String targetAlias)
throws ConversionException;
/**
* Return all conversion executors capable of converting source objects of the the specified
* <code>sourceClass</code>.
* @param sourceClass the source class to convert from
* @return the matching conversion executors
* @throws ConversionException an exception occured retrieving the converters
*/
public ConversionExecutor[] getConversionExecutorsForSource(Class sourceClass) throws ConversionException;
/**
* Return the class with the specified alias.
* @param alias the class alias
* @return the class, or null if not aliased
* @throws ConversionException when an error occurs looking up the class by alias
*/
public Class getClassByAlias(String alias) throws ConversionException;
}

View File

@@ -1,108 +0,0 @@
/*
* Copyright 2004-2007 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.support;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.springframework.binding.convert.ConversionException;
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.binding.convert.ConversionService;
import org.springframework.util.Assert;
/**
* A conversion service that delegates to an ordered chain of other conversion services. The first correct reply
* received from a conversion service in the chain is returned to the caller.
*
* @author Erwin Vervaet
*/
public class CompositeConversionService implements ConversionService {
private ConversionService[] chain;
/**
* Create a new composite conversion service.
* @param conversionServices the conversion services in the chain
*/
public CompositeConversionService(ConversionService[] conversionServices) {
Assert.notNull(conversionServices, "The conversion services chain is required");
this.chain = conversionServices;
}
/**
* Returns the conversion services in the chain managed by this composite conversion service.
*/
public ConversionService[] getConversionServices() {
return chain;
}
public ConversionExecutor getConversionExecutor(Class sourceClass, Class targetClass) throws ConversionException {
for (int i = 0; i < chain.length; i++) {
try {
return chain[i].getConversionExecutor(sourceClass, targetClass);
} catch (ConversionException e) {
// ignore and try the next conversion service in the chain
}
}
throw new ConversionException(sourceClass, targetClass, "No converter registered to convert from sourceClass '"
+ sourceClass + "' to target class '" + targetClass + "'");
}
public ConversionExecutor getConversionExecutorByTargetAlias(Class sourceClass, String targetAlias)
throws ConversionException {
boolean exceptionThrown = false;
for (int i = 0; i < chain.length; i++) {
try {
ConversionExecutor res = chain[i].getConversionExecutorByTargetAlias(sourceClass, targetAlias);
if (res != null) {
return res;
}
} catch (ConversionException e) {
exceptionThrown = true;
}
}
if (exceptionThrown) {
throw new ConversionException(sourceClass, "No converter registered to convert from sourceClass '"
+ sourceClass + "' to aliased target type '" + targetAlias + "'");
} else {
// alias was not recognized by any conversion service in the chain
return null;
}
}
public ConversionExecutor[] getConversionExecutorsForSource(Class sourceClass) throws ConversionException {
Set executors = new HashSet();
for (int i = 0; i < chain.length; i++) {
executors.addAll(Arrays.asList(chain[i].getConversionExecutorsForSource(sourceClass)));
}
return (ConversionExecutor[]) executors.toArray(new ConversionExecutor[executors.size()]);
}
public Class getClassByAlias(String alias) throws ConversionException {
for (int i = 0; i < chain.length; i++) {
try {
Class res = chain[i].getClassByAlias(alias);
if (res != null) {
return res;
}
} catch (ConversionException e) {
// ignore and try the next conversion service in the chain
}
}
return null;
}
}

View File

@@ -1,33 +0,0 @@
/*
* Copyright 2004-2007 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.support;
import org.springframework.binding.convert.ConversionService;
/**
* Marker interface that denotes an object has a dependency on a conversion service that is expected to be fulfilled.
*
* @author Keith Donald
*/
public interface ConversionServiceAware {
/**
* Set the conversion service this object should be made aware of (as it presumably depends on it).
*
* @param conversionService the conversion service
*/
public void setConversionService(ConversionService conversionService);
}

View File

@@ -1,100 +0,0 @@
/*
* Copyright 2004-2007 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.support;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.binding.expression.Expression;
/**
* Base class for converters that use other converters to convert things, thus they are conversion-service aware.
*
* @author Keith Donald
*/
public abstract class ConversionServiceAwareConverter extends AbstractConverter implements ConversionServiceAware {
/**
* The conversion service this converter is aware of.
*/
private ConversionService conversionService;
/**
* Default constructor, expects to conversion service to be injected using
* {@link #setConversionService(ConversionService)}.
*/
protected ConversionServiceAwareConverter() {
}
/**
* Create a converter using given conversion service.
*/
protected ConversionServiceAwareConverter(ConversionService conversionService) {
setConversionService(conversionService);
}
/**
* Returns the conversion service used.
*/
public ConversionService getConversionService() {
if (conversionService == null) {
throw new IllegalStateException("Conversion service not yet set: set it first before calling this method");
}
return conversionService;
}
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
/**
* Returns a conversion executor capable of converting string objects to the specified target class.
* @param targetClass the target class
* @return the conversion executor, never null
*/
protected ConversionExecutor fromStringTo(Class targetClass) {
return getConversionService().getConversionExecutor(String.class, targetClass);
}
/**
* Returns a conversion executor capable of converting string objects to the target class aliased by the provided
* alias.
* @param targetAlias the target class alias, e.g "long" or "float"
* @return the conversion executor, or <code>null</code> if no suitable converter exists for alias
*/
protected ConversionExecutor fromStringToAliased(String targetAlias) {
return getConversionService().getConversionExecutorByTargetAlias(String.class, targetAlias);
}
/**
* Returns a conversion executor capable of converting objects from one class to another.
* @param sourceClass the source class to convert from
* @param targetClass the target class to convert to
* @return the conversion executor, never null
*/
protected ConversionExecutor converterFor(Class sourceClass, Class targetClass) {
return getConversionService().getConversionExecutor(sourceClass, targetClass);
}
/**
* Helper that parsers the given expression string into an expression, using the installed String-&gt;Expression
* converter.
* @param expressionString the expression string to parse
* @return the parsed, evaluatable expression
*/
protected Expression parseExpression(String expressionString) {
return (Expression) fromStringTo(Expression.class).execute(expressionString);
}
}

View File

@@ -1,64 +0,0 @@
/*
* Copyright 2004-2005 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.support;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.util.Assert;
/**
* Registers all 'from string' converters known to a conversion service with a Spring bean factory.
* <p>
* Acts as bean factory post processor, registering property editor adapters for each supported conversion with a
* <code>java.lang.String sourceClass</code>. This makes for very convenient use with the Spring container.
*
* @author Keith Donald
*/
public class CustomConverterConfigurer implements BeanFactoryPostProcessor, InitializingBean {
private ConversionService conversionService;
/**
* Set the conversion service.
* @param conversionService the conversion service to take converters from
*/
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(conversionService, "The conversion service is required");
}
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
final ConversionExecutor[] executors = conversionService.getConversionExecutorsForSource(String.class);
PropertyEditorRegistrar registrar = new PropertyEditorRegistrar() {
public void registerCustomEditors(PropertyEditorRegistry registry) {
for (int i = 0; i < executors.length; i++) {
ConverterPropertyEditorAdapter editor = new ConverterPropertyEditorAdapter(executors[i]);
registry.registerCustomEditor(editor.getTargetClass(), editor);
}
}
};
beanFactory.addPropertyEditorRegistrar(registrar);
}
}

View File

@@ -15,14 +15,10 @@
*/
package org.springframework.binding.convert.support;
import java.math.BigDecimal;
import java.math.BigInteger;
import org.springframework.binding.format.FormatterRegistry;
import org.springframework.binding.format.factories.DateFormatterFactory;
import org.springframework.binding.format.factories.NumberFormatterFactory;
import org.springframework.binding.format.impl.FormatterRegistryImpl;
import org.springframework.core.enums.LabeledEnum;
/**
* Default, local implementation of a conversion service. Will automatically register <i>from string</i> converters for
@@ -54,19 +50,6 @@ public class DefaultConversionService extends GenericConversionService {
addConverter(new TextToLabeledEnum());
addConverter(new TextToNumber(formatterRegistry));
addConverter(new TextToDate(formatterRegistry));
addAlias("string", String.class);
addAlias("short", Short.class);
addAlias("integer", Integer.class);
addAlias("int", Integer.class);
addAlias("byte", Byte.class);
addAlias("long", Long.class);
addAlias("float", Float.class);
addAlias("double", Double.class);
addAlias("bigInteger", BigInteger.class);
addAlias("bigDecimal", BigDecimal.class);
addAlias("boolean", Boolean.class);
addAlias("class", Class.class);
addAlias("labeledEnum", LabeledEnum.class);
}
protected FormatterRegistry createDefaultFormatterRegistry() {

View File

@@ -15,22 +15,16 @@
*/
package org.springframework.binding.convert.support;
import java.util.Arrays;
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;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.convert.Converter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* Base implementation of a conversion service. Initially empty, e.g. no converters are registered by default.
@@ -46,11 +40,6 @@ public class GenericConversionService implements ConversionService {
*/
private Map sourceClassConverters = new HashMap();
/**
* A map of string aliases to convertible classes. Allows lookup of converters by alias.
*/
private Map aliasMap = new HashMap();
/**
* An optional parent conversion service.
*/
@@ -71,8 +60,8 @@ public class GenericConversionService implements ConversionService {
}
/**
* Add given converter to this conversion service. If the converter is {@link ConversionServiceAware}, it will get
* the conversion service injected.
* Add given converter to this conversion service.
* @param converter the converter
*/
public void addConverter(Converter converter) {
Class[] sourceClasses = converter.getSourceClasses();
@@ -89,44 +78,6 @@ public class GenericConversionService implements ConversionService {
sourceMap.put(targetClass, converter);
}
}
if (converter instanceof ConversionServiceAware) {
((ConversionServiceAware) converter).setConversionService(this);
}
}
/**
* Add all given converters. If the converters are {@link ConversionServiceAware}, they will get the conversion
* service injected.
*/
public void addConverters(Converter[] converters) {
for (int i = 0; i < converters.length; i++) {
addConverter(converters[i]);
}
}
/**
* Add given converter with an alias to the conversion service. If the converter is {@link ConversionServiceAware},
* it will get the conversion service injected.
*/
public void addConverter(Converter converter, String alias) {
aliasMap.put(alias, converter);
addConverter(converter);
}
/**
* Add an alias for given target type.
*/
public void addAlias(String alias, Class targetType) {
Assert.isTrue(!targetType.isPrimitive(), "Primitive types cannot be registered");
aliasMap.put(alias, targetType);
}
/**
* Generate a conventions based alias for given target type. For instance, "java.lang.Boolean" will get the
* "boolean" alias.
*/
public void addDefaultAlias(Class targetType) {
addAlias(StringUtils.uncapitalize(ClassUtils.getShortName(targetType)), targetType);
}
public ConversionExecutor getConversionExecutor(Class sourceClass, Class targetClass) throws ConversionException {
@@ -157,72 +108,6 @@ public class GenericConversionService implements ConversionService {
}
}
public ConversionExecutor getConversionExecutorByTargetAlias(Class sourceClass, String alias)
throws IllegalArgumentException {
Assert.notNull(sourceClass, "The source class to convert from is required");
Assert.hasText(alias, "The target alias is required and must either be a type alias (e.g 'boolean') "
+ "or a generic converter alias (e.g. 'bean') ");
Object targetType = aliasMap.get(alias);
if (targetType == null) {
if (parent != null) {
// try the parent
return parent.getConversionExecutorByTargetAlias(sourceClass, alias);
} else {
// not aliased
return null;
}
} else if (targetType instanceof Class) {
return getConversionExecutor(sourceClass, (Class) targetType);
} else {
Assert.isInstanceOf(Converter.class, targetType, "Not a converter: ");
Converter converter = (Converter) targetType;
return new ConversionExecutorImpl(sourceClass, Object.class, converter);
}
}
public ConversionExecutor[] getConversionExecutorsForSource(Class sourceClass) {
Assert.notNull(sourceClass, "The source class to convert from is required");
Map sourceTargetConverters = findConvertersForSource(sourceClass);
if (sourceTargetConverters.isEmpty()) {
if (parent != null) {
// use the parent
return parent.getConversionExecutorsForSource(sourceClass);
} else {
// no converters for source class
return new ConversionExecutor[0];
}
} else {
Set executors = new HashSet();
if (parent != null) {
executors.addAll(Arrays.asList(parent.getConversionExecutorsForSource(sourceClass)));
}
Iterator it = sourceTargetConverters.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
executors.add(new ConversionExecutorImpl(sourceClass, (Class) entry.getKey(), (Converter) entry
.getValue()));
}
return (ConversionExecutor[]) executors.toArray(new ConversionExecutor[executors.size()]);
}
}
public Class getClassByAlias(String alias) {
Assert.hasText(alias, "The alias is required and must be a type alias (e.g 'boolean')");
Object clazz = aliasMap.get(alias);
if (clazz != null) {
Assert.isInstanceOf(Class.class, clazz, "Not a Class alias '" + alias + "': ");
return (Class) clazz;
} else {
if (parent != null) {
// try parent service
return parent.getClassByAlias(alias);
} else {
// alias does not index a class, return null
return null;
}
}
}
// subclassing support
/**
@@ -235,11 +120,13 @@ public class GenericConversionService implements ConversionService {
}
/**
* Returns a map of known aliases. Each entry key is a String alias and the associated value is either a target
* class or a converter.
* Returns a registered converter object
* @param sourceClass the source class
* @param targetClass the target class
*/
protected Map getAliasMap() {
return aliasMap;
protected Converter getConverter(Class sourceClass, Class targetClass) {
Map sourceTargetConverters = findConvertersForSource(sourceClass);
return findTargetConverter(sourceTargetConverters, targetClass);
}
// internal helpers

View File

@@ -15,8 +15,13 @@
*/
package org.springframework.binding.convert.support;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import org.springframework.binding.convert.ConversionContext;
import org.springframework.util.Assert;
import org.springframework.core.enums.LabeledEnum;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
@@ -25,11 +30,20 @@ import org.springframework.util.StringUtils;
*
* @author Keith Donald
*/
public class TextToClass extends ConversionServiceAwareConverter {
public class TextToClass extends AbstractConverter {
private static final String ALIAS_PREFIX = "type:";
private Map aliasMap = new HashMap();
private static final String CLASS_PREFIX = "class:";
public TextToClass() {
addDefaultAliases();
}
/**
* Add an alias for given target type.
*/
public void addAlias(String alias, Class targetType) {
aliasMap.put(alias, targetType);
}
public Class[] getSourceClasses() {
return new Class[] { String.class };
@@ -42,27 +56,30 @@ public class TextToClass extends ConversionServiceAwareConverter {
protected Object doConvert(Object source, Class targetClass, ConversionContext context) throws Exception {
String text = (String) source;
if (StringUtils.hasText(text)) {
String classNameOrAlias = text.trim();
if (classNameOrAlias.startsWith(CLASS_PREFIX)) {
return ClassUtils.forName(text.substring(CLASS_PREFIX.length()));
} else if (classNameOrAlias.startsWith(ALIAS_PREFIX)) {
String alias = text.substring(ALIAS_PREFIX.length());
Class clazz = getConversionService().getClassByAlias(alias);
Assert.notNull(clazz, "No class found associated with type alias '" + alias + "'");
return clazz;
text = text.trim();
if (aliasMap.containsKey(text)) {
return aliasMap.get(text);
} else {
// try first an aliased based lookup
if (getConversionService() != null) {
Class aliasedClass = getConversionService().getClassByAlias(classNameOrAlias);
if (aliasedClass != null) {
return aliasedClass;
}
}
// treat as a class name
return ClassUtils.forName(classNameOrAlias);
return ClassUtils.forName(text);
}
} else {
return null;
}
}
protected void addDefaultAliases() {
addAlias("string", String.class);
addAlias("short", Short.class);
addAlias("integer", Integer.class);
addAlias("int", Integer.class);
addAlias("byte", Byte.class);
addAlias("long", Long.class);
addAlias("float", Float.class);
addAlias("double", Double.class);
addAlias("bigInteger", BigInteger.class);
addAlias("bigDecimal", BigDecimal.class);
addAlias("boolean", Boolean.class);
addAlias("class", Class.class);
addAlias("labeledEnum", LabeledEnum.class);
}
}