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);
}
}

View File

@@ -1,82 +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.Date;
import junit.framework.TestCase;
import org.springframework.binding.convert.ConversionException;
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.binding.convert.ConversionService;
/**
* Test case for the {@link CompositeConversionService}.
*
* @author Erwin Vervaet
*/
public class CompositeConversionServiceTests extends TestCase {
private CompositeConversionService service;
protected void setUp() throws Exception {
GenericConversionService first = new GenericConversionService();
first.addConverter(new TextToClass());
first.addConverter(new TextToBoolean("ja", "nee"));
first.addDefaultAlias(Boolean.class);
GenericConversionService second = new GenericConversionService();
second.addConverter(new TextToBoolean());
second.addDefaultAlias(Integer.class);
service = new CompositeConversionService(new ConversionService[] { first, second });
}
public void testGetConversionExecutor() {
assertNotNull(service.getConversionExecutor(String.class, Class.class));
assertNotNull(service.getConversionExecutor(String.class, Boolean.class));
assertEquals(Boolean.TRUE, service.getConversionExecutor(String.class, Boolean.class).execute("ja"));
try {
service.getConversionExecutor(String.class, Date.class);
fail();
} catch (ConversionException e) {
// expected
}
}
public void testGetConversionExecutorByTargetAlias() {
assertNotNull(service.getConversionExecutorByTargetAlias(String.class, "boolean"));
assertEquals(Boolean.TRUE, service.getConversionExecutorByTargetAlias(String.class, "boolean").execute("ja"));
assertNull(service.getConversionExecutorByTargetAlias(String.class, "class"));
}
public void testGetConversionExecutorsForSource() {
assertEquals(new TextToClass().getTargetClasses().length + new TextToBoolean().getTargetClasses().length,
service.getConversionExecutorsForSource(String.class).length);
assertEquals(0, service.getConversionExecutorsForSource(Date.class).length);
ConversionExecutor[] fromStringConversionExecutors = service.getConversionExecutorsForSource(String.class);
ConversionExecutorImpl booleanConversionExecutor = null;
for (int i = 0; i < fromStringConversionExecutors.length; i++) {
if (((ConversionExecutorImpl) fromStringConversionExecutors[i]).getConverter() instanceof TextToBoolean) {
booleanConversionExecutor = (ConversionExecutorImpl) fromStringConversionExecutors[i];
}
}
assertEquals(Boolean.TRUE, booleanConversionExecutor.execute("ja"));
}
public void testGetClassByAlias() {
assertEquals(Boolean.class, service.getClassByAlias("boolean"));
assertNull(service.getClassByAlias("class"));
}
}

View File

@@ -34,11 +34,9 @@ public class DefaultConversionServiceTests extends TestCase {
public void testConvertCompatibleTypes() {
DefaultConversionService service = new DefaultConversionService();
service.addAlias("list", List.class);
List lst = new ArrayList();
assertSame(lst, service.getConversionExecutor(ArrayList.class, List.class).execute(lst));
assertSame(lst, service.getConversionExecutorByTargetAlias(ArrayList.class, "list").execute(lst));
try {
service.getConversionExecutor(List.class, ArrayList.class);

View File

@@ -1,7 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry kind="src" path="src/main/resources"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
<classpathentry kind="src" path="src/test/resources"/>
<classpathentry combineaccessrules="false" kind="src" path="/spring-binding"/>
<classpathentry combineaccessrules="false" kind="src" path="/spring-webflow"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>

View File

@@ -14,6 +14,7 @@ package org.springframework.faces.model.converter;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.convert.support.DefaultConversionService;
import org.springframework.binding.convert.support.TextToClass;
import org.springframework.faces.model.OneSelectionTrackingListDataModel;
/**
@@ -25,13 +26,12 @@ import org.springframework.faces.model.OneSelectionTrackingListDataModel;
public class FacesConversionService extends DefaultConversionService {
public FacesConversionService() {
super();
addFacesConverters();
}
protected void addFacesConverters() {
addConverter(new DataModelConverter());
addAlias("dataModel", OneSelectionTrackingListDataModel.class);
TextToClass classConverter = (TextToClass) getConverter(String.class, Class.class);
classConverter.addAlias("dataModel", OneSelectionTrackingListDataModel.class);
}
}

View File

@@ -1,7 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry kind="src" output="target/classes" path="src/main/resources"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/resources"/>
<classpathentry combineaccessrules="false" kind="src" path="/spring-binding"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="var" path="IVY_CACHE/edu.oswego.cs/concurrent/concurrent-1.3.4.jar" sourcepath="IVY_CACHE/edu.oswego.cs/concurrent/concurrent-sources-1.3.4.jar"/>

View File

@@ -177,7 +177,8 @@ class FlowExecutorFactoryBean implements FactoryBean, InitializingBean {
}
/**
* Sets the conversion service for converting string-encoded flow execution attributes to typed values.
* TODO never called. Sets the conversion service for converting string-encoded flow execution attributes to typed
* values.
* @param conversionService the conversion service
*/
public void setConversionService(ConversionService conversionService) {
@@ -351,11 +352,15 @@ class FlowExecutorFactoryBean implements FactoryBean, InitializingBean {
private Object getConvertedValue(FlowElementAttribute attribute) {
if (attribute.needsTypeConversion()) {
ConversionExecutor converter = conversionService.getConversionExecutorByTargetAlias(String.class, attribute
.getType());
Class targetType = fromStringToClass(attribute.getType());
ConversionExecutor converter = conversionService.getConversionExecutor(String.class, targetType);
return converter.execute(attribute.getValue());
} else {
return attribute.getValue();
}
}
private Class fromStringToClass(String type) {
return (Class) conversionService.getConversionExecutor(String.class, Class.class).execute(type);
}
}

View File

@@ -151,14 +151,20 @@ class FlowRegistryFactoryBean implements FactoryBean, InitializingBean {
private Object getConvertedValue(FlowElementAttribute attribute) {
if (attribute.needsTypeConversion()) {
ConversionExecutor converter = flowBuilderServices.getConversionService()
.getConversionExecutorByTargetAlias(String.class, attribute.getType());
Class targetType = fromStringToClass(attribute.getType());
ConversionExecutor converter = flowBuilderServices.getConversionService().getConversionExecutor(
String.class, targetType);
return converter.execute(attribute.getValue());
} else {
return attribute.getValue();
}
}
private Class fromStringToClass(String type) {
return (Class) flowBuilderServices.getConversionService().getConversionExecutor(String.class, Class.class)
.execute(type);
}
private FlowDefinition buildFlowDefinition(FlowBuilderInfo builderInfo) {
try {
Class flowBuilderClass = ClassUtils.forName(builderInfo.getClassName());

View File

@@ -0,0 +1,60 @@
package org.springframework.webflow.mvc;
import org.springframework.binding.expression.Expression;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.ContextResource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.ClassUtils;
import org.springframework.web.servlet.view.InternalResourceView;
import org.springframework.web.servlet.view.JstlView;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.View;
import org.springframework.webflow.execution.ViewFactory;
/**
* View factory implementation that creates a Spring-MVC Internal Resource view to render a flow-relative view resource
* such as a JSP or Velocity template.
* @author Keith Donald
*/
class InternalFlowResourceMvcViewFactory implements ViewFactory {
private static final boolean JSTL_PRESENT = ClassUtils.isPresent("javax.servlet.jsp.jstl.fmt.LocalizationContext");
private Expression viewIdExpression;
private ApplicationContext applicationContext;
private ResourceLoader resourceLoader;
public InternalFlowResourceMvcViewFactory(Expression viewIdExpression, ApplicationContext context,
ResourceLoader resourceLoader) {
this.viewIdExpression = viewIdExpression;
this.applicationContext = context;
this.resourceLoader = resourceLoader;
}
public View getView(RequestContext context) {
String viewId = (String) viewIdExpression.getValue(context);
if (viewId.startsWith("/")) {
return getViewInternal(viewId, context);
} else {
ContextResource viewResource = (ContextResource) resourceLoader.getResource(viewId);
return getViewInternal(viewResource.getPathWithinContext(), context);
}
}
private View getViewInternal(String viewPath, RequestContext context) {
if (viewPath.endsWith(".jsp")) {
if (JSTL_PRESENT) {
JstlView view = new JstlView(viewPath);
view.setApplicationContext(applicationContext);
return new MvcView(view, context);
} else {
InternalResourceView view = new InternalResourceView(viewPath);
view.setApplicationContext(applicationContext);
return new MvcView(view, context);
}
} else {
throw new IllegalArgumentException("Unsupported view type " + viewPath + " only types supported are [.jsp]");
}
}
}

View File

@@ -0,0 +1,249 @@
/*
* 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.webflow.mvc;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.binding.collection.MapAdaptable;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.binding.expression.support.ParserContextImpl;
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.binding.mapping.MappingResult;
import org.springframework.binding.mapping.MappingResults;
import org.springframework.binding.mapping.MappingResultsCriteria;
import org.springframework.binding.mapping.impl.DefaultMapper;
import org.springframework.binding.mapping.impl.DefaultMapping;
import org.springframework.binding.mapping.results.TargetAccessError;
import org.springframework.validation.BindingResult;
import org.springframework.webflow.core.collection.ParameterMap;
import org.springframework.webflow.engine.Transition;
import org.springframework.webflow.engine.TransitionableState;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.View;
import org.springframework.webflow.expression.DefaultExpressionParserFactory;
class MvcView implements View {
private static final MappingResultsCriteria PROPERTY_NOT_FOUND_ERRORS = new PropertyNotFoundErrors();
private org.springframework.web.servlet.View view;
private RequestContext context;
private ExpressionParser expressionParser = DefaultExpressionParserFactory.getExpressionParser();
private FormatterRegistry formatterRegistry;
private MappingResults mappingResults;
private boolean postbackSuccess;
private String eventId;
public MvcView(org.springframework.web.servlet.View view, RequestContext context) {
this.view = view;
this.context = context;
this.formatterRegistry = createDefaultFormatterRegistry();
}
public void setExpressionParser(ExpressionParser expressionParser) {
this.expressionParser = expressionParser;
}
public void setFormatterRegistry(FormatterRegistry formatterRegistry) {
this.formatterRegistry = formatterRegistry;
}
public void render() throws IOException {
Map model = new HashMap();
model.putAll(flowScopes());
exposeBindingModel(model);
model.put("flowRequestContext", context);
model.put("flowExecutionKey", context.getFlowExecutionContext().getKey().toString());
model.put("flowExecutionUrl", context.getFlowExecutionUrl());
model.put("currentUser", context.getExternalContext().getCurrentUser());
try {
view.render(model, (HttpServletRequest) context.getExternalContext().getNativeRequest(),
(HttpServletResponse) context.getExternalContext().getNativeResponse());
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException("Unexpected exception occurred rendering view " + view, e);
}
}
public void postback() {
determineEventId(context);
Object model = getModelObject();
if (model == null) {
postbackSuccess = true;
return;
}
if (shouldBind(model)) {
mappingResults = bind(model);
if (!mappingResults.hasErrorResults()) {
postbackSuccess = true;
} else {
if (onlyPropertyNotFoundErrors()) {
postbackSuccess = true;
} else {
postbackSuccess = false;
}
}
}
}
private boolean onlyPropertyNotFoundErrors() {
return mappingResults.getResults(PROPERTY_NOT_FOUND_ERRORS).size() == mappingResults.getErrorResults().size();
}
public boolean eventSignaled() {
return postbackSuccess && eventId != null;
}
public Event getEvent() {
return new Event(this, eventId, context.getRequestParameters().asAttributeMap());
}
protected FormatterRegistry createDefaultFormatterRegistry() {
FormatterRegistryImpl registry = new FormatterRegistryImpl();
registry.registerFormatter(new NumberFormatterFactory());
registry.registerFormatter(new DateFormatterFactory());
return registry;
}
private Map flowScopes() {
return context.getConversationScope().union(context.getFlowScope()).union(context.getFlashScope()).union(
context.getRequestScope()).asMap();
}
private void exposeBindingModel(Map model) {
Object modelObject = getModelObject();
if (modelObject != null) {
BindingModel bindingModel = new BindingModel(modelObject, expressionParser, formatterRegistry, context
.getMessageContext());
bindingModel.setMappingResults(mappingResults);
model.put(BindingResult.MODEL_KEY_PREFIX + getModelExpression().getExpressionString(), model);
}
}
private Object getModelObject() {
Expression model = getModelExpression();
if (model != null) {
return model.getValue(context);
} else {
return null;
}
}
private Expression getModelExpression() {
return (Expression) context.getCurrentState().getAttributes().get("model");
}
private boolean shouldBind(Object model) {
// this downcast is not ideal
TransitionableState currentState = (TransitionableState) context.getCurrentState();
// TODO this won't work because last event isn't set yet...
Transition transition = currentState.getRequiredTransition(context);
if (transition.getAttributes().contains("bind")) {
return transition.getAttributes().getBoolean("bind").booleanValue();
} else {
return false;
}
}
private MappingResults bind(Object model) {
DefaultMapper mapper = new DefaultMapper();
addDefaultMappings(mapper, context.getRequestParameters(), model);
return mapper.map(context.getRequestParameters(), model);
}
private void addDefaultMappings(DefaultMapper mapper, ParameterMap requestParameters, Object model) {
for (Iterator it = requestParameters.asMap().keySet().iterator(); it.hasNext();) {
String name = (String) it.next();
Expression source = expressionParser
.parseExpression(name, new ParserContextImpl().eval(MapAdaptable.class));
Expression target = expressionParser.parseExpression(name, new ParserContextImpl().eval(model.getClass()));
mapper.addMapping(new DefaultMapping(source, target));
}
}
private void determineEventId(RequestContext context) {
eventId = findParameter("_eventId", context.getRequestParameters());
}
/**
* Obtain a named parameter from the request parameters. This method will try to obtain a parameter value using the
* following algorithm:
* <ol>
* <li>Try to get the parameter value using just the given <i>logical</i> name. This handles parameters of the
* form <tt>logicalName = value</tt>. For normal parameters, e.g. submitted using a hidden HTML form field, this
* will return the requested value.</li>
* <li>Try to obtain the parameter value from the parameter name, where the parameter name in the request is of the
* form <tt>logicalName_value = xyz</tt> with "_" being the configured delimiter. This deals with parameter values
* submitted using an HTML form submit button.</li>
* <li>If the value obtained in the previous step has a ".x" or ".y" suffix, remove that. This handles cases where
* the value was submitted using an HTML form image button. In this case the parameter in the request would actually
* be of the form <tt>logicalName_value.x = 123</tt>. </li>
* </ol>
* @param logicalParameterName the <i>logical</i> name of the request parameter
* @param parameters the available parameter map
* @return the value of the parameter, or <code>null</code> if the parameter does not exist in given request
*/
private String findParameter(String logicalParameterName, ParameterMap parameters) {
// first try to get it as a normal name=value parameter
String value = parameters.get(logicalParameterName);
if (value != null) {
return value;
}
// if no value yet, try to get it as a name_value=xyz parameter
String prefix = logicalParameterName + "_";
Iterator paramNames = parameters.asMap().keySet().iterator();
while (paramNames.hasNext()) {
String paramName = (String) paramNames.next();
if (paramName.startsWith(prefix)) {
String strValue = paramName.substring(prefix.length());
// support images buttons, which would submit parameters as
// name_value.x=123
if (strValue.endsWith(".x") || strValue.endsWith(".y")) {
strValue = strValue.substring(0, strValue.length() - 2);
}
return strValue;
}
}
// we couldn't find the parameter value
return null;
}
private static class PropertyNotFoundErrors implements MappingResultsCriteria {
public boolean test(MappingResult result) {
return result.getResult() instanceof TargetAccessError
&& result.getResult().getErrorCode().equals("propertyNotFound");
}
}
}

View File

@@ -15,33 +15,15 @@
*/
package org.springframework.webflow.mvc;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.binding.format.FormatterRegistry;
import org.springframework.binding.mapping.MappingResults;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.io.ContextResource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.ClassUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.InternalResourceView;
import org.springframework.web.servlet.view.JstlView;
import org.springframework.webflow.core.collection.ParameterMap;
import org.springframework.webflow.engine.builder.ViewFactoryCreator;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.View;
import org.springframework.webflow.execution.ViewFactory;
/**
@@ -58,12 +40,8 @@ import org.springframework.webflow.execution.ViewFactory;
*/
public class MvcViewFactoryCreator implements ViewFactoryCreator, ApplicationContextAware {
private static final boolean jstlPresent = ClassUtils.isPresent("javax.servlet.jsp.jstl.fmt.LocalizationContext");
// TODO
private ExpressionParser expressionParser;
// TODO
private FormatterRegistry formatterRegistry;
private List viewResolvers;
@@ -96,193 +74,4 @@ public class MvcViewFactoryCreator implements ViewFactoryCreator, ApplicationCon
return viewStateId + ".jsp";
}
/**
* View factory implementation that creates a Spring-MVC Internal Resource view to render a flow-relative view
* resource such as a JSP or Velocity template.
* @author Keith Donald
*/
static class InternalFlowResourceMvcViewFactory implements ViewFactory {
private Expression viewIdExpression;
private ApplicationContext applicationContext;
private ResourceLoader resourceLoader;
public InternalFlowResourceMvcViewFactory(Expression viewIdExpression, ApplicationContext context,
ResourceLoader resourceLoader) {
this.viewIdExpression = viewIdExpression;
this.applicationContext = context;
this.resourceLoader = resourceLoader;
}
public View getView(RequestContext context) {
String viewId = (String) viewIdExpression.getValue(context);
if (viewId.startsWith("/")) {
return getViewInternal(viewId, context);
} else {
ContextResource viewResource = (ContextResource) resourceLoader.getResource(viewId);
return getViewInternal(viewResource.getPathWithinContext(), context);
}
}
private View getViewInternal(String viewPath, RequestContext context) {
if (viewPath.endsWith(".jsp")) {
if (jstlPresent) {
JstlView view = new JstlView(viewPath);
view.setApplicationContext(applicationContext);
return new MvcView(view, context);
} else {
InternalResourceView view = new InternalResourceView(viewPath);
view.setApplicationContext(applicationContext);
return new MvcView(view, context);
}
} else {
throw new IllegalArgumentException("Unsupported view type " + viewPath
+ " only types supported are [.jsp]");
}
}
}
/**
* View factory implementation that delegates to the Spring-configured view resolver chain to resolve the Spring MVC
* view implementation to render.
* @author Keith Donald
*/
private static class ViewResolvingMvcViewFactory implements ViewFactory {
private Expression viewIdExpression;
private List viewResolvers;
public ViewResolvingMvcViewFactory(Expression viewIdExpression, List viewResolvers) {
this.viewIdExpression = viewIdExpression;
this.viewResolvers = viewResolvers;
}
public View getView(RequestContext context) {
String view = (String) viewIdExpression.getValue(context);
return new MvcView(resolveView(view), context);
}
protected org.springframework.web.servlet.View resolveView(String viewName) {
for (Iterator it = viewResolvers.iterator(); it.hasNext();) {
ViewResolver viewResolver = (ViewResolver) it.next();
try {
org.springframework.web.servlet.View view = viewResolver.resolveViewName(viewName,
LocaleContextHolder.getLocale());
if (view != null) {
return view;
}
} catch (Exception e) {
throw new IllegalStateException("Exception resolving view with name '" + viewName + "'", e);
}
}
return null;
}
}
private static class MvcView implements View {
private RequestContext context;
private org.springframework.web.servlet.View view;
private String eventId;
public MvcView(org.springframework.web.servlet.View view, RequestContext context) {
this.view = view;
this.context = context;
}
public void render() {
Map model = new HashMap();
model.putAll(context.getConversationScope().union(context.getFlowScope()).union(context.getFlashScope())
.union(context.getRequestScope()).asMap());
exposeBindingModel(model);
model.put("flowRequestContext", context);
model.put("flowExecutionKey", context.getFlowExecutionContext().getKey().toString());
model.put("flowExecutionUrl", context.getFlowExecutionUrl());
model.put("currentUser", context.getExternalContext().getCurrentUser());
try {
view.render(model, (HttpServletRequest) context.getExternalContext().getNativeRequest(),
(HttpServletResponse) context.getExternalContext().getNativeResponse());
} catch (Exception e) {
throw new IllegalStateException("Exception rendering view", e);
}
}
public void postback() {
// TODO implement me with real data binding behavior
}
public boolean eventSignaled() {
determineEventId(context);
return eventId != null;
}
public Event getEvent() {
return new Event(this, eventId, context.getRequestParameters().asAttributeMap());
}
private void exposeBindingModel(Map model) {
Expression boundObjectExpr = (Expression) context.getCurrentState().getAttributes().get("model");
if (boundObjectExpr != null) {
Object boundObject = boundObjectExpr.getValue(context);
// TODO
BindingModel bindingModel = new BindingModel(boundObject, null, null, context.getMessageContext());
MappingResults bindResults = (MappingResults) context.getRequestScope().get(
boundObjectExpr.getExpressionString() + "MappingResults");
bindingModel.setMappingResults(bindResults);
model.put(BindingResult.MODEL_KEY_PREFIX + boundObjectExpr.getExpressionString(), model);
}
}
private void determineEventId(RequestContext context) {
eventId = findParameter("_eventId", context.getRequestParameters());
}
/**
* Obtain a named parameter from the request parameters. This method will try to obtain a parameter value using
* the following algorithm:
* <ol>
* <li>Try to get the parameter value using just the given <i>logical</i> name. This handles parameters of the
* form <tt>logicalName = value</tt>. For normal parameters, e.g. submitted using a hidden HTML form field,
* this will return the requested value.</li>
* <li>Try to obtain the parameter value from the parameter name, where the parameter name in the request is of
* the form <tt>logicalName_value = xyz</tt> with "_" being the configured delimiter. This deals with
* parameter values submitted using an HTML form submit button.</li>
* <li>If the value obtained in the previous step has a ".x" or ".y" suffix, remove that. This handles cases
* where the value was submitted using an HTML form image button. In this case the parameter in the request
* would actually be of the form <tt>logicalName_value.x = 123</tt>. </li>
* </ol>
* @param logicalParameterName the <i>logical</i> name of the request parameter
* @param parameters the available parameter map
* @return the value of the parameter, or <code>null</code> if the parameter does not exist in given request
*/
private String findParameter(String logicalParameterName, ParameterMap parameters) {
// first try to get it as a normal name=value parameter
String value = parameters.get(logicalParameterName);
if (value != null) {
return value;
}
// if no value yet, try to get it as a name_value=xyz parameter
String prefix = logicalParameterName + "_";
Iterator paramNames = parameters.asMap().keySet().iterator();
while (paramNames.hasNext()) {
String paramName = (String) paramNames.next();
if (paramName.startsWith(prefix)) {
String strValue = paramName.substring(prefix.length());
// support images buttons, which would submit parameters as
// name_value.x=123
if (strValue.endsWith(".x") || strValue.endsWith(".y")) {
strValue = strValue.substring(0, strValue.length() - 2);
}
return strValue;
}
}
// we couldn't find the parameter value
return null;
}
}
}

View File

@@ -0,0 +1,48 @@
package org.springframework.webflow.mvc;
import java.util.Iterator;
import java.util.List;
import org.springframework.binding.expression.Expression;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.View;
import org.springframework.webflow.execution.ViewFactory;
/**
* View factory implementation that delegates to the Spring-configured view resolver chain to resolve the Spring MVC
* view implementation to render.
* @author Keith Donald
*/
class ViewResolvingMvcViewFactory implements ViewFactory {
private Expression viewIdExpression;
private List viewResolvers;
public ViewResolvingMvcViewFactory(Expression viewIdExpression, List viewResolvers) {
this.viewIdExpression = viewIdExpression;
this.viewResolvers = viewResolvers;
}
public View getView(RequestContext context) {
String view = (String) viewIdExpression.getValue(context);
return new MvcView(resolveView(view), context);
}
protected org.springframework.web.servlet.View resolveView(String viewName) {
for (Iterator it = viewResolvers.iterator(); it.hasNext();) {
ViewResolver viewResolver = (ViewResolver) it.next();
try {
org.springframework.web.servlet.View view = viewResolver.resolveViewName(viewName,
LocaleContextHolder.getLocale());
if (view != null) {
return view;
}
} catch (Exception e) {
throw new IllegalStateException("Exception resolving view with name '" + viewName + "'", e);
}
}
return null;
}
}