bind action
attribute mapper enhancements
This commit is contained in:
@@ -117,6 +117,7 @@ public class GenericConversionService implements ConversionService {
|
||||
* 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);
|
||||
}
|
||||
|
||||
@@ -137,6 +138,8 @@ public class GenericConversionService implements ConversionService {
|
||||
if (targetClass.isAssignableFrom(sourceClass)) {
|
||||
return new ConversionExecutorImpl(sourceClass, targetClass, new NoOpConverter(sourceClass, targetClass));
|
||||
}
|
||||
sourceClass = convertToWrapperClassIfNecessary(sourceClass);
|
||||
targetClass = convertToWrapperClassIfNecessary(targetClass);
|
||||
Map sourceTargetConverters = findConvertersForSource(sourceClass);
|
||||
Converter converter = findTargetConverter(sourceTargetConverters, targetClass);
|
||||
if (converter != null) {
|
||||
@@ -220,6 +223,25 @@ public class GenericConversionService implements ConversionService {
|
||||
}
|
||||
}
|
||||
|
||||
// subclassing support
|
||||
|
||||
/**
|
||||
* Returns an indexed map of converters. Each entry key is a source class that can be converted from, and each entry
|
||||
* value is a map of target classes that can be convertered to, ultimately mapping to a specific converter that can
|
||||
* perform the source->target conversion.
|
||||
*/
|
||||
protected Map getSourceClassConverters() {
|
||||
return sourceClassConverters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of known aliases. Each entry key is a String alias and the associated value is either a target
|
||||
* class or a converter.
|
||||
*/
|
||||
protected Map getAliasMap() {
|
||||
return aliasMap;
|
||||
}
|
||||
|
||||
// internal helpers
|
||||
|
||||
private Map findConvertersForSource(Class sourceClass) {
|
||||
@@ -264,22 +286,29 @@ public class GenericConversionService implements ConversionService {
|
||||
return null;
|
||||
}
|
||||
|
||||
// subclassing support
|
||||
|
||||
/**
|
||||
* Returns an indexed map of converters. Each entry key is a source class that can be converted from, and each entry
|
||||
* value is a map of target classes that can be convertered to, ultimately mapping to a specific converter that can
|
||||
* perform the source->target conversion.
|
||||
*/
|
||||
protected Map getSourceClassConverters() {
|
||||
return sourceClassConverters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of known aliases. Each entry key is a String alias and the associated value is either a target
|
||||
* class or a converter.
|
||||
*/
|
||||
protected Map getAliasMap() {
|
||||
return aliasMap;
|
||||
private Class convertToWrapperClassIfNecessary(Class targetType) {
|
||||
if (targetType.isPrimitive()) {
|
||||
if (targetType.equals(int.class)) {
|
||||
return Integer.class;
|
||||
} else if (targetType.equals(short.class)) {
|
||||
return Short.class;
|
||||
} else if (targetType.equals(long.class)) {
|
||||
return Long.class;
|
||||
} else if (targetType.equals(float.class)) {
|
||||
return Float.class;
|
||||
} else if (targetType.equals(double.class)) {
|
||||
return Double.class;
|
||||
} else if (targetType.equals(byte.class)) {
|
||||
return Byte.class;
|
||||
} else if (targetType.equals(boolean.class)) {
|
||||
return Boolean.class;
|
||||
} else if (targetType.equals(char.class)) {
|
||||
return Character.class;
|
||||
} else {
|
||||
throw new IllegalStateException("Should never happen - primitive type is not a primitive?");
|
||||
}
|
||||
} else {
|
||||
return targetType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ public class TextToNumber extends AbstractFormattingConverter {
|
||||
}
|
||||
|
||||
public Class[] getTargetClasses() {
|
||||
return new Class[] { Integer.class, Short.class, Byte.class, Long.class, Float.class, Double.class,
|
||||
return new Class[] { Integer.class, Short.class, Long.class, Float.class, Double.class, Byte.class,
|
||||
BigInteger.class, BigDecimal.class };
|
||||
}
|
||||
|
||||
|
||||
@@ -33,9 +33,24 @@ public interface Expression {
|
||||
|
||||
/**
|
||||
* Set this expression in the provided context to the value provided.
|
||||
* @param context the context to apply this value to
|
||||
* @param context the context to set this value to
|
||||
* @param value the new value to be set
|
||||
* @throws EvaluationException an exception occurred during evaluation
|
||||
*/
|
||||
public void setValue(Object context, Object value) throws EvaluationException;
|
||||
|
||||
/**
|
||||
* Returns the most general type that can be passed to the {@link #setValue(Object, Object)} method for the given
|
||||
* context.
|
||||
* @param context the context of expression evaluation
|
||||
* @return the most general type of value that can be set
|
||||
*/
|
||||
public Class getValueType(Object context);
|
||||
|
||||
/**
|
||||
* Returns the original string used to create this expression, unmodified.
|
||||
* @return the original expression string
|
||||
*/
|
||||
public String getExpressionString();
|
||||
|
||||
}
|
||||
@@ -41,7 +41,11 @@ public class DefaultELResolver extends CompositeELResolver {
|
||||
}
|
||||
|
||||
public Class getType(ELContext context, Object base, Object property) {
|
||||
return super.getType(context, base, property);
|
||||
if (base == null) {
|
||||
return super.getType(context, target, property);
|
||||
} else {
|
||||
return super.getType(context, base, property);
|
||||
}
|
||||
}
|
||||
|
||||
public Object getValue(ELContext context, Object base, Object property) {
|
||||
|
||||
@@ -21,16 +21,21 @@ public class ELExpression implements Expression {
|
||||
|
||||
private ValueExpression valueExpression;
|
||||
|
||||
private boolean template;
|
||||
|
||||
/**
|
||||
* Creates a new el expression
|
||||
* @param factory the el context factory for creating the EL context that will be used during expression evaluation
|
||||
* @param valueExpression the value expression to evaluate
|
||||
* @param template whether or not this expression is a template expression; if not it was parsed as an implict eval
|
||||
* expression (without delimiters)
|
||||
*/
|
||||
public ELExpression(ELContextFactory factory, ValueExpression valueExpression) {
|
||||
public ELExpression(ELContextFactory factory, ValueExpression valueExpression, boolean template) {
|
||||
Assert.notNull(factory, "The ELContextFactory is required to evaluate EL expressions");
|
||||
Assert.notNull(valueExpression, "The EL value expression is required for evaluation");
|
||||
this.elContextFactory = factory;
|
||||
this.valueExpression = valueExpression;
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
public Object getValue(Object context) throws EvaluationException {
|
||||
@@ -58,6 +63,24 @@ public class ELExpression implements Expression {
|
||||
}
|
||||
}
|
||||
|
||||
public Class getValueType(Object context) {
|
||||
ELContext ctx = elContextFactory.getELContext(context);
|
||||
try {
|
||||
return valueExpression.getType(ctx);
|
||||
} catch (ELException ex) {
|
||||
throw new EvaluationException(new EvaluationAttempt(this, context), ex);
|
||||
}
|
||||
}
|
||||
|
||||
public String getExpressionString() {
|
||||
if (template) {
|
||||
return valueExpression.getExpressionString();
|
||||
} else {
|
||||
String rawExpressionString = valueExpression.getExpressionString();
|
||||
return rawExpressionString.substring("#{".length(), rawExpressionString.length() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return valueExpression.hashCode();
|
||||
}
|
||||
|
||||
@@ -56,20 +56,21 @@ public class ELExpressionParser implements ExpressionParser {
|
||||
context = NullParserContext.INSTANCE;
|
||||
}
|
||||
if (context.isTemplate()) {
|
||||
return parseTemplate(expressionString, context);
|
||||
return parseExpressionInternal(expressionString, context, true);
|
||||
} else {
|
||||
assertNotDelimited(expressionString);
|
||||
assertHasText(expressionString);
|
||||
return parseTemplate("#{" + expressionString + "}", context);
|
||||
return parseExpressionInternal("#{" + expressionString + "}", context, false);
|
||||
}
|
||||
}
|
||||
|
||||
private Expression parseTemplate(String expressionString, ParserContext context) throws ParserException {
|
||||
private Expression parseExpressionInternal(String expressionString, ParserContext context, boolean template)
|
||||
throws ParserException {
|
||||
Assert.notNull(expressionString, "The expression string to parse is required");
|
||||
try {
|
||||
ValueExpression expression = parseValueExpression(expressionString, context);
|
||||
ELContextFactory contextFactory = getContextFactory(context.getEvaluationContextType(), expressionString);
|
||||
return new ELExpression(contextFactory, expression);
|
||||
return new ELExpression(contextFactory, expression, template);
|
||||
} catch (ELException e) {
|
||||
throw new ParserException(expressionString, e);
|
||||
}
|
||||
|
||||
@@ -52,13 +52,18 @@ class OgnlExpression implements Expression {
|
||||
private Class expectedResultType;
|
||||
|
||||
/**
|
||||
* Creates a new OGNL expression.
|
||||
* @param expression the parsed expression
|
||||
* The original expression string.
|
||||
*/
|
||||
public OgnlExpression(Object expression, Map variableExpressions, Class expectedResultType) {
|
||||
private String expressionString;
|
||||
|
||||
/**
|
||||
* Creates a new OGNL expression.
|
||||
*/
|
||||
public OgnlExpression(Object expression, Map variableExpressions, Class expectedResultType, String expressionString) {
|
||||
this.expression = expression;
|
||||
this.variableExpressions = variableExpressions;
|
||||
this.expectedResultType = expectedResultType;
|
||||
this.expressionString = expressionString;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
@@ -98,6 +103,23 @@ class OgnlExpression implements Expression {
|
||||
}
|
||||
}
|
||||
|
||||
public Class getValueType(Object context) {
|
||||
try {
|
||||
if (Ognl.isSimpleProperty(expression)) {
|
||||
// TODO
|
||||
throw new UnsupportedOperationException("Not yet implemented - in progress");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (OgnlException e) {
|
||||
throw new EvaluationException(new EvaluationAttempt(this, context), e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getExpressionString() {
|
||||
return expressionString;
|
||||
}
|
||||
|
||||
private Map getVariables(Object context) {
|
||||
if (variableExpressions == null) {
|
||||
return Collections.EMPTY_MAP;
|
||||
|
||||
@@ -244,7 +244,7 @@ public class OgnlExpressionParser implements ExpressionParser {
|
||||
}
|
||||
try {
|
||||
return new OgnlExpression(Ognl.parseExpression(expressionString), parseVariableExpressions(context
|
||||
.getExpressionVariables()), context.getExpectedEvaluationResultType());
|
||||
.getExpressionVariables()), context.getExpectedEvaluationResultType(), expressionString);
|
||||
} catch (OgnlException e) {
|
||||
throw new ParserException(expressionString, e);
|
||||
}
|
||||
|
||||
@@ -19,4 +19,12 @@ public abstract class AbstractGetValueExpression implements Expression {
|
||||
throw new UnsupportedOperationException("Setting this expression's value is not supported");
|
||||
}
|
||||
|
||||
public Class getValueType(Object context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getExpressionString() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -60,6 +60,14 @@ public class CollectionAddingExpression implements Expression {
|
||||
}
|
||||
}
|
||||
|
||||
public Class getValueType(Object context) {
|
||||
return Object.class;
|
||||
}
|
||||
|
||||
public String getExpressionString() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("collectionExpression", collectionExpression).toString();
|
||||
}
|
||||
|
||||
@@ -54,6 +54,14 @@ public class CompositeStringExpression implements Expression {
|
||||
"Cannot set a composite string expression value"));
|
||||
}
|
||||
|
||||
public Class getValueType(Object context) {
|
||||
return String.class;
|
||||
}
|
||||
|
||||
public String getExpressionString() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("expressions", expressions).toString();
|
||||
}
|
||||
|
||||
@@ -43,6 +43,14 @@ public class LiteralExpression implements Expression {
|
||||
+ "If so, should the expression string be enclosed in eval delimiters?"));
|
||||
}
|
||||
|
||||
public Class getValueType(Object context) {
|
||||
return String.class;
|
||||
}
|
||||
|
||||
public String getExpressionString() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "literal('" + literal + "')";
|
||||
}
|
||||
|
||||
@@ -55,14 +55,22 @@ public final class StaticExpression implements Expression {
|
||||
return ObjectUtils.nullSafeEquals(value, other.value);
|
||||
}
|
||||
|
||||
public Object getValue(Object target) throws EvaluationException {
|
||||
public Object getValue(Object context) throws EvaluationException {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(Object target, Object value) throws EvaluationException {
|
||||
public void setValue(Object context, Object value) throws EvaluationException {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Class getValueType(Object context) {
|
||||
return Object.class;
|
||||
}
|
||||
|
||||
public String getExpressionString() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import java.text.ParseException;
|
||||
|
||||
import org.springframework.binding.format.Formatter;
|
||||
import org.springframework.binding.format.InvalidFormatException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -29,38 +28,29 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public abstract class AbstractFormatter implements Formatter {
|
||||
|
||||
/**
|
||||
* Does this formatter allow empty values?
|
||||
*/
|
||||
private boolean allowEmpty = true;
|
||||
|
||||
/**
|
||||
* Constructs a formatter.
|
||||
*/
|
||||
protected AbstractFormatter() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a formatter.
|
||||
* @param allowEmpty allow formatting of empty (null or blank) values?
|
||||
*/
|
||||
protected AbstractFormatter(boolean allowEmpty) {
|
||||
this.allowEmpty = allowEmpty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow formatting of empty (null or blank) values?
|
||||
*/
|
||||
public boolean isAllowEmpty() {
|
||||
return allowEmpty;
|
||||
}
|
||||
|
||||
public final String formatValue(Object value) {
|
||||
if (allowEmpty && isEmpty(value)) {
|
||||
if (isEmpty(value)) {
|
||||
return getEmptyFormattedValue();
|
||||
} else {
|
||||
return doFormatValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
public final Object parseValue(String formattedString, Class targetClass) throws InvalidFormatException {
|
||||
try {
|
||||
if (isEmpty(formattedString)) {
|
||||
return getEmptyValue();
|
||||
}
|
||||
return doParseValue(formattedString, targetClass);
|
||||
} catch (ParseException ex) {
|
||||
throw new InvalidFormatException(formattedString, getExpectedFormat(targetClass), ex);
|
||||
}
|
||||
Assert.isTrue(!isEmpty(value), "Object to format cannot be empty");
|
||||
return doFormatValue(value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,22 +67,12 @@ public abstract class AbstractFormatter implements Formatter {
|
||||
return "";
|
||||
}
|
||||
|
||||
public final Object parseValue(String formattedString, Class targetClass) throws InvalidFormatException {
|
||||
try {
|
||||
if (allowEmpty && isEmpty(formattedString)) {
|
||||
return getEmptyValue();
|
||||
}
|
||||
return doParseValue(formattedString, targetClass);
|
||||
} catch (ParseException ex) {
|
||||
throw new InvalidFormatException(formattedString, getExpectedFormat(targetClass), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Template method subclasses should override to encapsulate parsing logic.
|
||||
* @param formattedString the formatted string to parse
|
||||
* @param targetClass the target class to convert the formatted value to
|
||||
* @return the parsed value
|
||||
* @throws InvalidFormatException an exception occured parsing
|
||||
* @throws InvalidFormatException an exception occurred parsing
|
||||
* @throws ParseException when parse exceptions occur
|
||||
*/
|
||||
protected abstract Object doParseValue(String formattedString, Class targetClass) throws InvalidFormatException,
|
||||
|
||||
@@ -38,16 +38,6 @@ public class DateFormatter extends AbstractFormatter {
|
||||
this.dateFormat = dateFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a date formatter that will delegate to the specified date format.
|
||||
* @param dateFormat the date format to use
|
||||
* @param allowEmpty should this formatter allow empty input arguments?
|
||||
*/
|
||||
public DateFormatter(DateFormat dateFormat, boolean allowEmpty) {
|
||||
super(allowEmpty);
|
||||
this.dateFormat = dateFormat;
|
||||
}
|
||||
|
||||
// convert from date to string
|
||||
protected String doFormatValue(Object date) {
|
||||
return dateFormat.format((Date) date);
|
||||
|
||||
@@ -36,14 +36,6 @@ public class LabeledEnumFormatter extends AbstractFormatter {
|
||||
public LabeledEnumFormatter() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new LabeledEnum formatter.
|
||||
* @param allowEmpty should this formatter allow empty input arguments?
|
||||
*/
|
||||
public LabeledEnumFormatter(boolean allowEmpty) {
|
||||
super(allowEmpty);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the LabeledEnumResolver used. Defaults to {@link StaticLabeledEnumResolver}.
|
||||
*/
|
||||
@@ -58,13 +50,7 @@ public class LabeledEnumFormatter extends AbstractFormatter {
|
||||
}
|
||||
|
||||
protected Object doParseValue(String formattedString, Class targetClass) throws IllegalArgumentException {
|
||||
LabeledEnum labeledEnum = labeledEnumResolver.getLabeledEnumByLabel(targetClass, formattedString);
|
||||
if (!isAllowEmpty()) {
|
||||
Assert.notNull(labeledEnum, "The label '" + formattedString
|
||||
+ "' did not map to a valid enum instance for type " + targetClass);
|
||||
Assert.isInstanceOf(targetClass, labeledEnum);
|
||||
}
|
||||
return labeledEnum;
|
||||
return labeledEnumResolver.getLabeledEnumByLabel(targetClass, formattedString);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,13 +31,6 @@ public class NumberFormatter extends AbstractFormatter {
|
||||
|
||||
private NumberFormat numberFormat;
|
||||
|
||||
/**
|
||||
* Default constructor. The formatter will use "toString" when formatting a value and "valueOf" when parsing a
|
||||
* value.
|
||||
*/
|
||||
public NumberFormatter() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new number formatter.
|
||||
* @param numberFormat the number format to use
|
||||
@@ -46,16 +39,6 @@ public class NumberFormatter extends AbstractFormatter {
|
||||
this.numberFormat = numberFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new number formatter.
|
||||
* @param numberFormat the number format to use
|
||||
* @param allowEmpty should this formatter allow empty input arguments?
|
||||
*/
|
||||
public NumberFormatter(NumberFormat numberFormat, boolean allowEmpty) {
|
||||
super(allowEmpty);
|
||||
this.numberFormat = numberFormat;
|
||||
}
|
||||
|
||||
protected String doFormatValue(Object number) {
|
||||
if (this.numberFormat != null) {
|
||||
// use NumberFormat for rendering value
|
||||
@@ -67,12 +50,11 @@ public class NumberFormatter extends AbstractFormatter {
|
||||
}
|
||||
|
||||
protected Object doParseValue(String text, Class targetClass) throws IllegalArgumentException {
|
||||
// use given NumberFormat for parsing text
|
||||
if (this.numberFormat != null) {
|
||||
// use given NumberFormat for parsing text
|
||||
return NumberUtils.parseNumber(text, targetClass, this.numberFormat);
|
||||
}
|
||||
// use default valueOf methods for parsing text
|
||||
else {
|
||||
} else {
|
||||
// use default valueOf methods for parsing text
|
||||
return NumberUtils.parseNumber(text, targetClass);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,11 @@ public class SimpleFormatterFactory extends AbstractFormatterFactory {
|
||||
}
|
||||
|
||||
public Formatter getNumberFormatter(Class numberClass) {
|
||||
return new NumberFormatter(NumberFormat.getNumberInstance(getLocale()));
|
||||
if (numberClass.equals(Integer.class) || numberClass.equals(int.class)) {
|
||||
return new NumberFormatter(NumberFormat.getIntegerInstance(getLocale()));
|
||||
} else {
|
||||
return new NumberFormatter(NumberFormat.getNumberInstance(getLocale()));
|
||||
}
|
||||
}
|
||||
|
||||
public Formatter getCurrencyFormatter() {
|
||||
|
||||
@@ -30,6 +30,7 @@ public interface AttributeMapper {
|
||||
* @param source the source
|
||||
* @param target the target
|
||||
* @param context the mapping context
|
||||
* @throws AttributeMappingException if errors occurred during the mapping process
|
||||
*/
|
||||
public void map(Object source, Object target, MappingContext context);
|
||||
public void map(Object source, Object target, MappingContext context) throws AttributeMappingException;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.springframework.binding.mapping;
|
||||
|
||||
public class AttributeMappingException extends RuntimeException {
|
||||
|
||||
}
|
||||
@@ -38,10 +38,10 @@ public class DefaultAttributeMapper implements AttributeMapper {
|
||||
|
||||
/**
|
||||
* Add a mapping to this mapper.
|
||||
* @param mapping the mapping to add (as an AttributeMapper)
|
||||
* @param mapping the mapping to add
|
||||
* @return this, to support convenient call chaining
|
||||
*/
|
||||
public DefaultAttributeMapper addMapping(AttributeMapper mapping) {
|
||||
public DefaultAttributeMapper addMapping(Mapping mapping) {
|
||||
mappings.add(mapping);
|
||||
return this;
|
||||
}
|
||||
@@ -50,7 +50,7 @@ public class DefaultAttributeMapper implements AttributeMapper {
|
||||
* Add a set of mappings.
|
||||
* @param mappings the mappings
|
||||
*/
|
||||
public void addMappings(AttributeMapper[] mappings) {
|
||||
public void addMappings(Mapping[] mappings) {
|
||||
if (mappings == null) {
|
||||
return;
|
||||
}
|
||||
@@ -61,18 +61,25 @@ public class DefaultAttributeMapper implements AttributeMapper {
|
||||
* Returns this mapper's list of mappings.
|
||||
* @return the list of mappings
|
||||
*/
|
||||
public AttributeMapper[] getMappings() {
|
||||
return (AttributeMapper[]) mappings.toArray(new AttributeMapper[mappings.size()]);
|
||||
public Mapping[] getMappings() {
|
||||
return (Mapping[]) mappings.toArray(new Mapping[mappings.size()]);
|
||||
}
|
||||
|
||||
public void map(Object source, Object target, MappingContext context) {
|
||||
public void map(Object source, Object target, MappingContext context) throws AttributeMappingException {
|
||||
boolean mappingFailure = false;
|
||||
if (mappings != null) {
|
||||
Iterator it = mappings.iterator();
|
||||
while (it.hasNext()) {
|
||||
AttributeMapper mapping = (AttributeMapper) it.next();
|
||||
mapping.map(source, target, context);
|
||||
Mapping mapping = (Mapping) it.next();
|
||||
boolean result = mapping.map(source, target, context);
|
||||
if (!result && !mappingFailure) {
|
||||
mappingFailure = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mappingFailure) {
|
||||
throw new AttributeMappingException();
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
|
||||
@@ -17,10 +17,14 @@ package org.springframework.binding.mapping;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.binding.convert.ConversionException;
|
||||
import org.springframework.binding.convert.ConversionExecutor;
|
||||
import org.springframework.binding.expression.Expression;
|
||||
import org.springframework.binding.message.MessageBuilder;
|
||||
import org.springframework.binding.message.MessageResolver;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* A single mapping definition, encapsulating the information necessary to map the result of evaluating an expression on
|
||||
@@ -28,7 +32,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public class Mapping implements AttributeMapper {
|
||||
public class Mapping {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(Mapping.class);
|
||||
|
||||
@@ -85,29 +89,54 @@ public class Mapping implements AttributeMapper {
|
||||
* @param source The source data structure
|
||||
* @param target The target data structure
|
||||
*/
|
||||
public void map(Object source, Object target, MappingContext context) {
|
||||
// get source value
|
||||
public boolean map(Object source, Object target, MappingContext context) {
|
||||
Assert.notNull(source, "The source to map from is required");
|
||||
Assert.notNull(target, "The target to map to is required");
|
||||
Assert.notNull(context, "The mapping context is required");
|
||||
Object sourceValue = sourceExpression.getValue(source);
|
||||
if (sourceValue == null) {
|
||||
if (required) {
|
||||
throw new RequiredMappingException("This mapping is required; evaluation of expression '"
|
||||
+ sourceExpression + "' against source of type [" + source.getClass()
|
||||
+ "] must return a non-null value");
|
||||
} else {
|
||||
// source expression returned no value, simply abort mapping
|
||||
return;
|
||||
if (required && sourceValue == null || isEmptyString(sourceValue)) {
|
||||
String defaultText = "'" + targetExpression.getExpressionString() + "' is required";
|
||||
MessageResolver message = new MessageBuilder().error().source(targetExpression.getExpressionString())
|
||||
.codes(createMessageCodes("required", target, targetExpression)).defaultText(defaultText).build();
|
||||
context.getMessageContext().addMessage(message);
|
||||
return false;
|
||||
}
|
||||
Object targetValue;
|
||||
if (sourceValue != null && typeConverter != null) {
|
||||
try {
|
||||
targetValue = typeConverter.execute(sourceValue);
|
||||
} catch (ConversionException e) {
|
||||
String defaultText = "The '" + targetExpression.getExpressionString() + "' value is the wrong type";
|
||||
MessageResolver message = new MessageBuilder().error().source(targetExpression.getExpressionString())
|
||||
.codes(createMessageCodes("typeMismatch", target, targetExpression)).defaultText(defaultText)
|
||||
.build();
|
||||
context.getMessageContext().addMessage(message);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
targetValue = sourceValue;
|
||||
}
|
||||
Object targetValue = sourceValue;
|
||||
if (typeConverter != null) {
|
||||
targetValue = typeConverter.execute(sourceValue);
|
||||
}
|
||||
// set target value
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Mapping '" + sourceExpression + "' value [" + sourceValue + "] to target property '"
|
||||
+ targetExpression + "'; setting property value to [" + targetValue + "]");
|
||||
logger.debug("Mapping '" + sourceExpression + "' value [" + sourceValue + "] to target '"
|
||||
+ targetExpression + "'; setting target value to [" + targetValue + "]");
|
||||
}
|
||||
targetExpression.setValue(target, targetValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isEmptyString(Object sourceValue) {
|
||||
if (sourceValue instanceof CharSequence) {
|
||||
return ((CharSequence) sourceValue).length() == 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private String[] createMessageCodes(String errorCode, Object target, Expression targetExpression) {
|
||||
String[] codes = new String[2];
|
||||
codes[0] = ClassUtils.getShortName(target.getClass()) + "." + targetExpression.getExpressionString();
|
||||
codes[1] = errorCode;
|
||||
return codes;
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.binding.mapping;
|
||||
|
||||
import org.springframework.binding.message.MessageContext;
|
||||
|
||||
/**
|
||||
* A context object with two main responsibities:
|
||||
* <ol>
|
||||
@@ -27,4 +29,10 @@ package org.springframework.binding.mapping;
|
||||
*/
|
||||
public interface MappingContext {
|
||||
|
||||
/**
|
||||
* Returns the message context to use to record errors during the mapping process.
|
||||
* @return the message context
|
||||
*/
|
||||
public MessageContext getMessageContext();
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.springframework.binding.mapping;
|
||||
|
||||
import org.springframework.binding.message.MessageContext;
|
||||
|
||||
public class MappingContextImpl implements MappingContext {
|
||||
|
||||
private MessageContext messageContext;
|
||||
|
||||
public MappingContextImpl(MessageContext messageContext) {
|
||||
this.messageContext = messageContext;
|
||||
}
|
||||
|
||||
public MessageContext getMessageContext() {
|
||||
return messageContext;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.mapping;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Unit tests for the {@link org.springframework.binding.mapping.RequiredMapping}.
|
||||
*/
|
||||
public class MappingTests extends TestCase {
|
||||
|
||||
public void testMapping() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,62 +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.mapping;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.binding.expression.ognl.OgnlExpressionParser;
|
||||
|
||||
/**
|
||||
* Unit tests for the {@link org.springframework.binding.mapping.RequiredMapping}.
|
||||
*/
|
||||
public class RequiredMappingTests extends TestCase {
|
||||
|
||||
public void testRequired() {
|
||||
MappingBuilder builder = new MappingBuilder(new OgnlExpressionParser());
|
||||
Mapping mapping = builder.source("foo").target("bar").required().value();
|
||||
HashMap source = new HashMap();
|
||||
source.put("foo", "baz");
|
||||
HashMap target = new HashMap();
|
||||
mapping.map(source, target, null);
|
||||
assertEquals("baz", target.get("bar"));
|
||||
}
|
||||
|
||||
public void testRequiredExceptionOnNull() {
|
||||
MappingBuilder builder = new MappingBuilder(new OgnlExpressionParser());
|
||||
Mapping mapping = builder.source("foo").target("bar").required().value();
|
||||
HashMap source = new HashMap();
|
||||
source.put("foo", null);
|
||||
HashMap target = new HashMap();
|
||||
try {
|
||||
mapping.map(source, target, null);
|
||||
} catch (RequiredMappingException e) {
|
||||
}
|
||||
}
|
||||
|
||||
public void testRequiredExceptionOnNoKey() {
|
||||
MappingBuilder builder = new MappingBuilder(new OgnlExpressionParser());
|
||||
Mapping mapping = builder.source("foo").target("bar").required().value();
|
||||
HashMap source = new HashMap();
|
||||
HashMap target = new HashMap();
|
||||
try {
|
||||
mapping.map(source, target, null);
|
||||
} catch (RequiredMappingException e) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,6 +17,7 @@ package org.springframework.webflow.action;
|
||||
|
||||
import org.springframework.binding.mapping.AttributeMapper;
|
||||
import org.springframework.binding.mapping.MappingContext;
|
||||
import org.springframework.binding.mapping.MappingContextImpl;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.webflow.execution.Event;
|
||||
import org.springframework.webflow.execution.RequestContext;
|
||||
@@ -63,6 +64,6 @@ public class AttributeMapperAction extends AbstractAction {
|
||||
* returns null. Subclasses can override this if necessary.
|
||||
*/
|
||||
protected MappingContext getMappingContext(RequestContext context) {
|
||||
return null;
|
||||
return new MappingContextImpl(context.getMessageContext());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.springframework.webflow.action;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.springframework.binding.convert.ConversionService;
|
||||
import org.springframework.binding.convert.support.RuntimeBindingConversionExecutor;
|
||||
import org.springframework.binding.expression.Expression;
|
||||
import org.springframework.binding.expression.ExpressionParser;
|
||||
import org.springframework.binding.expression.support.ParserContextImpl;
|
||||
import org.springframework.binding.mapping.AttributeMappingException;
|
||||
import org.springframework.binding.mapping.DefaultAttributeMapper;
|
||||
import org.springframework.binding.mapping.Mapping;
|
||||
import org.springframework.binding.mapping.MappingContextImpl;
|
||||
import org.springframework.webflow.core.collection.AttributeMap;
|
||||
import org.springframework.webflow.execution.Event;
|
||||
import org.springframework.webflow.execution.RequestContext;
|
||||
|
||||
public class BindAction extends AbstractAction {
|
||||
|
||||
private Expression target;
|
||||
|
||||
private ExpressionParser expressionParser;
|
||||
|
||||
private ConversionService conversionService;
|
||||
|
||||
public BindAction(Expression target, ExpressionParser expressionParser, ConversionService conversionService) {
|
||||
this.target = target;
|
||||
this.expressionParser = expressionParser;
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
protected Event doExecute(final RequestContext context) throws Exception {
|
||||
Object target = this.target.getValue(context);
|
||||
if (target == null) {
|
||||
throw new IllegalStateException(
|
||||
"The bind target cannot be null - check your expression. Bind target expression = " + target);
|
||||
}
|
||||
DefaultAttributeMapper mapper = new DefaultAttributeMapper();
|
||||
AttributeMap eventAttributes = context.getLastEvent().getAttributes();
|
||||
for (Iterator it = eventAttributes.asMap().keySet().iterator(); it.hasNext();) {
|
||||
String name = (String) it.next();
|
||||
Expression sourceAttribute = expressionParser.parseExpression(name, new ParserContextImpl()
|
||||
.eval(AttributeMap.class));
|
||||
Expression targetAttribute = expressionParser.parseExpression(name, new ParserContextImpl().eval(target
|
||||
.getClass()));
|
||||
Class targetType = targetAttribute.getValueType(target);
|
||||
mapper.addMapping(new Mapping(sourceAttribute, targetAttribute, new RuntimeBindingConversionExecutor(
|
||||
targetType, conversionService), false));
|
||||
}
|
||||
try {
|
||||
mapper.map(context.getLastEvent().getAttributes(), target, new MappingContextImpl(context
|
||||
.getMessageContext()));
|
||||
return success();
|
||||
} catch (AttributeMappingException e) {
|
||||
return error();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package org.springframework.webflow.core;
|
||||
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
|
||||
/**
|
||||
* Root class for exceptions thrown by the Spring Web Flow system. All other exceptions within the system should be
|
||||
* assignable to this class.
|
||||
@@ -24,7 +22,7 @@ import org.springframework.core.NestedRuntimeException;
|
||||
* @author Keith Donald
|
||||
* @author Erwin Vervaet
|
||||
*/
|
||||
public abstract class FlowException extends NestedRuntimeException {
|
||||
public abstract class FlowException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Creates a new flow exception.
|
||||
|
||||
@@ -16,9 +16,8 @@
|
||||
package org.springframework.webflow.engine;
|
||||
|
||||
import org.springframework.binding.mapping.AttributeMapper;
|
||||
import org.springframework.binding.mapping.MappingContext;
|
||||
import org.springframework.binding.mapping.MappingContextImpl;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.webflow.core.collection.LocalAttributeMap;
|
||||
import org.springframework.webflow.execution.Action;
|
||||
import org.springframework.webflow.execution.Event;
|
||||
@@ -49,12 +48,12 @@ public class EndState extends State {
|
||||
/**
|
||||
* The renderer that will render the final response when a flow execution terminates.
|
||||
*/
|
||||
private Action finalResponseAction = new NullFinalResponseAction();
|
||||
private Action finalResponseAction;
|
||||
|
||||
/**
|
||||
* The attribute mapper for mapping output attributes exposed by this end state when it is entered.
|
||||
*/
|
||||
private AttributeMapper outputMapper = new NoOutputMapper();
|
||||
private AttributeMapper outputMapper;
|
||||
|
||||
/**
|
||||
* Create a new end state with no associated view.
|
||||
@@ -73,7 +72,6 @@ public class EndState extends State {
|
||||
* Sets the renderer that will render the final flow execution response.
|
||||
*/
|
||||
public void setFinalResponseAction(Action finalResponseAction) {
|
||||
Assert.notNull(finalResponseAction, "The final response action is required");
|
||||
this.finalResponseAction = finalResponseAction;
|
||||
}
|
||||
|
||||
@@ -81,7 +79,6 @@ public class EndState extends State {
|
||||
* Sets the attribute mapper to use for mapping output attributes exposed by this end state when it is entered.
|
||||
*/
|
||||
public void setOutputMapper(AttributeMapper outputMapper) {
|
||||
Assert.notNull(outputMapper, "The flow session output mapper is required");
|
||||
this.outputMapper = outputMapper;
|
||||
}
|
||||
|
||||
@@ -99,7 +96,9 @@ public class EndState extends State {
|
||||
FlowSession activeSession = context.getFlowExecutionContext().getActiveSession();
|
||||
if (activeSession.isRoot()) {
|
||||
// entire flow execution is ending; issue the final response
|
||||
ActionExecutor.execute(finalResponseAction, context);
|
||||
if (finalResponseAction != null) {
|
||||
ActionExecutor.execute(finalResponseAction, context);
|
||||
}
|
||||
context.endActiveFlowSession(createSessionOutput(context));
|
||||
} else {
|
||||
// there is a parent flow that will resume (this flow is a subflow)
|
||||
@@ -115,7 +114,9 @@ public class EndState extends State {
|
||||
*/
|
||||
protected LocalAttributeMap createSessionOutput(RequestContext context) {
|
||||
LocalAttributeMap outputMap = new LocalAttributeMap();
|
||||
outputMapper.map(context, outputMap, null);
|
||||
if (outputMapper != null) {
|
||||
outputMapper.map(context, outputMap, new MappingContextImpl(context.getMessageContext()));
|
||||
}
|
||||
return outputMap;
|
||||
}
|
||||
|
||||
@@ -123,29 +124,4 @@ public class EndState extends State {
|
||||
creator.append("finalResponseAction", finalResponseAction).append("outputMapper", outputMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders no response. The default implementation.
|
||||
*/
|
||||
private class NullFinalResponseAction implements Action {
|
||||
public Event execute(RequestContext context) {
|
||||
logger.debug("Not issuing a final response");
|
||||
return new Event(this, "success");
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps no output attributes. The default implementation.
|
||||
*/
|
||||
private class NoOutputMapper implements AttributeMapper {
|
||||
public void map(Object source, Object target, MappingContext context) {
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.support.StaticListableBeanFactory;
|
||||
import org.springframework.binding.mapping.AttributeMapper;
|
||||
import org.springframework.binding.mapping.MappingContextImpl;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.style.StylerUtils;
|
||||
@@ -511,7 +512,7 @@ public class Flow extends AnnotatedObject implements FlowDefinition {
|
||||
assertStartStateSet();
|
||||
createVariables(context);
|
||||
if (inputMapper != null) {
|
||||
inputMapper.map(input, context, null);
|
||||
inputMapper.map(input, context, new MappingContextImpl(context.getMessageContext()));
|
||||
}
|
||||
startActionList.execute(context);
|
||||
startState.enter(context);
|
||||
@@ -563,7 +564,7 @@ public class Flow extends AnnotatedObject implements FlowDefinition {
|
||||
public void end(RequestControlContext context, MutableAttributeMap output) throws FlowExecutionException {
|
||||
endActionList.execute(context);
|
||||
if (outputMapper != null) {
|
||||
outputMapper.map(context, output, null);
|
||||
outputMapper.map(context, output, new MappingContextImpl(context.getMessageContext()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -983,6 +983,14 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde
|
||||
public void setValue(Object context, Object value) throws EvaluationException {
|
||||
throw new UnsupportedOperationException("Cannot set a subflow expression");
|
||||
}
|
||||
|
||||
public Class getValueType(Object context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getExpressionString() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
|
||||
@@ -121,6 +121,15 @@ Goes out of scope when the governing conversation ends.
|
||||
|
||||
<xsd:group name="actionTypes">
|
||||
<xsd:choice>
|
||||
<xsd:element ref="bind">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
<![CDATA[
|
||||
Binds current event parameters to a target object associated with the flow. Also capable of performing a validation post-binding.
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
<xsd:element ref="evaluate">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
@@ -649,7 +658,21 @@ Handles exceptions that occur within this state.
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
|
||||
<xsd:element name="bind">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="target" type="expression" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
<![CDATA[
|
||||
The bind target.
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="evaluate">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="expression" type="expression" use="required">
|
||||
@@ -737,7 +760,7 @@ The expected value type. If the actual value type does not match the expected, a
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
<![CDATA[
|
||||
An attribute describing this transition.
|
||||
A meta attribute describing or otherwise annotating this transition.
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
|
||||
@@ -1,77 +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.webflow.engine.support;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.binding.mapping.AttributeMapper;
|
||||
import org.springframework.binding.mapping.MappingContext;
|
||||
import org.springframework.webflow.core.collection.AttributeMap;
|
||||
import org.springframework.webflow.core.collection.LocalAttributeMap;
|
||||
import org.springframework.webflow.core.collection.MutableAttributeMap;
|
||||
import org.springframework.webflow.engine.SubflowAttributeMapper;
|
||||
import org.springframework.webflow.execution.RequestContext;
|
||||
|
||||
/**
|
||||
* Convenient base class for subflow attribute mapper implementations. Encapsulates common attribute mapper workflow.
|
||||
* Contains no state. Subclasses must override the {@link #getInputMapper()} and {@link #getOutputMapper()} methods to
|
||||
* return the input mapper and output mapper, respectively.
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public abstract class AbstractSubflowAttributeMapper implements SubflowAttributeMapper, Serializable {
|
||||
|
||||
/**
|
||||
* Returns the input mapper to use to map attributes of a parent flow {@link RequestContext} to a subflow input
|
||||
* {@link AttributeMap attribute map}.
|
||||
* @return the input mapper, or null if none
|
||||
* @see #createFlowInput(RequestContext)
|
||||
*/
|
||||
protected abstract AttributeMapper getInputMapper();
|
||||
|
||||
/**
|
||||
* Returns the output mapper to use to map attributes from a subflow output {@link AttributeMap attribute map} to
|
||||
* the {@link RequestContext}.
|
||||
* @return the output mapper, or null if none
|
||||
* @see #mapFlowOutput(AttributeMap, RequestContext)
|
||||
*/
|
||||
protected abstract AttributeMapper getOutputMapper();
|
||||
|
||||
public MutableAttributeMap createFlowInput(RequestContext context) {
|
||||
AttributeMapper inputMapper = getInputMapper();
|
||||
if (inputMapper != null) {
|
||||
LocalAttributeMap input = new LocalAttributeMap();
|
||||
inputMapper.map(context, input, getMappingContext(context));
|
||||
return input;
|
||||
} else {
|
||||
return new LocalAttributeMap();
|
||||
}
|
||||
}
|
||||
|
||||
public void mapFlowOutput(AttributeMap subflowOutput, RequestContext context) {
|
||||
AttributeMapper outputMapper = getOutputMapper();
|
||||
if (getOutputMapper() != null && subflowOutput != null) {
|
||||
outputMapper.map(subflowOutput, context, getMappingContext(context));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of contextual data available during mapping. This implementation just returns null.
|
||||
*/
|
||||
protected MappingContext getMappingContext(RequestContext context) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -18,14 +18,20 @@ package org.springframework.webflow.engine.support;
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.binding.mapping.AttributeMapper;
|
||||
import org.springframework.binding.mapping.MappingContextImpl;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.webflow.core.collection.AttributeMap;
|
||||
import org.springframework.webflow.core.collection.LocalAttributeMap;
|
||||
import org.springframework.webflow.core.collection.MutableAttributeMap;
|
||||
import org.springframework.webflow.engine.SubflowAttributeMapper;
|
||||
import org.springframework.webflow.execution.RequestContext;
|
||||
|
||||
/**
|
||||
* Simple flow attribute mapper that holds an input and output mapper strategy.
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public final class GenericSubflowAttributeMapper extends AbstractSubflowAttributeMapper implements Serializable {
|
||||
public final class GenericSubflowAttributeMapper implements SubflowAttributeMapper, Serializable {
|
||||
|
||||
private final AttributeMapper inputMapper;
|
||||
|
||||
@@ -41,12 +47,20 @@ public final class GenericSubflowAttributeMapper extends AbstractSubflowAttribut
|
||||
this.outputMapper = outputMapper;
|
||||
}
|
||||
|
||||
protected AttributeMapper getInputMapper() {
|
||||
return inputMapper;
|
||||
public MutableAttributeMap createFlowInput(RequestContext context) {
|
||||
if (inputMapper != null) {
|
||||
LocalAttributeMap input = new LocalAttributeMap();
|
||||
inputMapper.map(context, input, new MappingContextImpl(context.getMessageContext()));
|
||||
return input;
|
||||
} else {
|
||||
return new LocalAttributeMap();
|
||||
}
|
||||
}
|
||||
|
||||
protected AttributeMapper getOutputMapper() {
|
||||
return outputMapper;
|
||||
public void mapFlowOutput(AttributeMap subflowOutput, RequestContext context) {
|
||||
if (outputMapper != null && subflowOutput != null) {
|
||||
outputMapper.map(subflowOutput, context, new MappingContextImpl(context.getMessageContext()));
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
|
||||
@@ -20,8 +20,6 @@ import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.core.JdkVersion;
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.webflow.engine.ActionList;
|
||||
@@ -137,63 +135,24 @@ public class TransitionExecutingFlowExecutionExceptionHandler implements FlowExe
|
||||
* Find the mapped target state resolver for given exception. Returns <code>null</code> if no mapping can be found
|
||||
* for given exception. Will try all exceptions in the exception cause chain.
|
||||
*/
|
||||
protected TargetStateResolver getTargetStateResolver(FlowExecutionException e) {
|
||||
if (JdkVersion.getMajorJavaVersion() == JdkVersion.JAVA_13) {
|
||||
return getTargetStateResolver13(e);
|
||||
} else {
|
||||
return getTargetStateResolver14(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal getTargetStateResolver implementation for use with JDK 1.3.
|
||||
*/
|
||||
private TargetStateResolver getTargetStateResolver13(NestedRuntimeException e) {
|
||||
protected TargetStateResolver getTargetStateResolver(Throwable e) {
|
||||
TargetStateResolver targetStateResolver;
|
||||
if (isRootCause13(e)) {
|
||||
if (isRootCause(e)) {
|
||||
return findTargetStateResolver(e.getClass());
|
||||
} else {
|
||||
targetStateResolver = (TargetStateResolver) exceptionTargetStateMappings.get(e.getClass());
|
||||
if (targetStateResolver != null) {
|
||||
return targetStateResolver;
|
||||
} else {
|
||||
if (e.getCause() instanceof NestedRuntimeException) {
|
||||
return getTargetStateResolver13((NestedRuntimeException) e.getCause());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return getTargetStateResolver(e.getCause());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal getTargetStateResolver implementation for use with JDK 1.4 or later.
|
||||
*/
|
||||
private TargetStateResolver getTargetStateResolver14(Throwable t) {
|
||||
TargetStateResolver targetStateResolver;
|
||||
if (isRootCause14(t)) {
|
||||
return findTargetStateResolver(t.getClass());
|
||||
} else {
|
||||
targetStateResolver = (TargetStateResolver) exceptionTargetStateMappings.get(t.getClass());
|
||||
if (targetStateResolver != null) {
|
||||
return targetStateResolver;
|
||||
} else {
|
||||
return getTargetStateResolver14(t.getCause());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given exception is the root of the exception cause chain. For use with JDK 1.3.
|
||||
*/
|
||||
private boolean isRootCause13(NestedRuntimeException e) {
|
||||
return e.getCause() == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given exception is the root of the exception cause chain. For use with JDK 1.4 or later.
|
||||
*/
|
||||
private boolean isRootCause14(Throwable t) {
|
||||
private boolean isRootCause(Throwable t) {
|
||||
return t.getCause() == null;
|
||||
}
|
||||
|
||||
@@ -214,43 +173,15 @@ public class TransitionExecutingFlowExecutionExceptionHandler implements FlowExe
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the root cause of given throwable.
|
||||
*/
|
||||
protected Throwable findRootCause(Throwable t) {
|
||||
if (JdkVersion.getMajorJavaVersion() == JdkVersion.JAVA_13) {
|
||||
return findRootCause13(t);
|
||||
} else {
|
||||
return findRootCause14(t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the root cause of given throwable. For use on JDK 1.3.
|
||||
*/
|
||||
private Throwable findRootCause13(Throwable t) {
|
||||
if (t instanceof NestedRuntimeException) {
|
||||
NestedRuntimeException nre = (NestedRuntimeException) t;
|
||||
Throwable cause = nre.getCause();
|
||||
if (cause == null) {
|
||||
return nre;
|
||||
} else {
|
||||
return findRootCause13(cause);
|
||||
}
|
||||
} else {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the root cause of given throwable. For use on JDK 1.4 or later.
|
||||
*/
|
||||
private Throwable findRootCause14(Throwable e) {
|
||||
private Throwable findRootCause(Throwable e) {
|
||||
Throwable cause = e.getCause();
|
||||
if (cause == null) {
|
||||
return e;
|
||||
} else {
|
||||
return findRootCause14(cause);
|
||||
return findRootCause(cause);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package org.springframework.webflow.action;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.binding.convert.ConversionService;
|
||||
import org.springframework.binding.convert.support.DefaultConversionService;
|
||||
import org.springframework.binding.expression.Expression;
|
||||
import org.springframework.binding.expression.ExpressionParser;
|
||||
import org.springframework.binding.expression.support.ParserContextImpl;
|
||||
import org.springframework.binding.message.Severity;
|
||||
import org.springframework.webflow.core.collection.LocalAttributeMap;
|
||||
import org.springframework.webflow.execution.Event;
|
||||
import org.springframework.webflow.execution.RequestContext;
|
||||
import org.springframework.webflow.expression.DefaultExpressionParserFactory;
|
||||
import org.springframework.webflow.test.MockRequestContext;
|
||||
|
||||
public class BindActionTests extends TestCase {
|
||||
private ExpressionParser expressionParser = DefaultExpressionParserFactory.getExpressionParser();
|
||||
private ConversionService conversionService = new DefaultConversionService();
|
||||
|
||||
private BindAction action;
|
||||
|
||||
public void testSuccessfulBind() throws Exception {
|
||||
MockRequestContext context = new MockRequestContext();
|
||||
context.getFlowScope().put("bindTarget", new BindBean());
|
||||
|
||||
Expression target = expressionParser.parseExpression("bindTarget", new ParserContextImpl()
|
||||
.eval(RequestContext.class));
|
||||
action = new BindAction(target, expressionParser, conversionService);
|
||||
|
||||
LocalAttributeMap eventData = new LocalAttributeMap();
|
||||
eventData.put("stringProperty", "foo");
|
||||
eventData.put("integerProperty", "3");
|
||||
Event event = new Event(this, "submit", eventData);
|
||||
context.setLastEvent(event);
|
||||
|
||||
Event result = action.execute(context);
|
||||
assertEquals("success", result.getId());
|
||||
|
||||
BindBean bean = (BindBean) context.getFlowScope().get("bindTarget");
|
||||
assertEquals("foo", bean.getStringProperty());
|
||||
assertEquals(3, bean.getIntegerProperty());
|
||||
}
|
||||
|
||||
public void testBindWithErrors() throws Exception {
|
||||
MockRequestContext context = new MockRequestContext();
|
||||
context.getFlowScope().put("bindTarget", new BindBean());
|
||||
|
||||
Expression target = expressionParser.parseExpression("bindTarget", new ParserContextImpl()
|
||||
.eval(RequestContext.class));
|
||||
action = new BindAction(target, expressionParser, conversionService);
|
||||
|
||||
LocalAttributeMap eventData = new LocalAttributeMap();
|
||||
eventData.put("stringProperty", "foo");
|
||||
eventData.put("integerProperty", "malformed");
|
||||
Event event = new Event(this, "submit", eventData);
|
||||
context.setLastEvent(event);
|
||||
|
||||
Event result = action.execute(context);
|
||||
assertEquals("error", result.getId());
|
||||
|
||||
BindBean bean = (BindBean) context.getFlowScope().get("bindTarget");
|
||||
assertEquals("foo", bean.getStringProperty());
|
||||
assertEquals(0, bean.getIntegerProperty());
|
||||
assertEquals(1, context.getMessageContext().getMessages().length);
|
||||
assertEquals("integerProperty", context.getMessageContext().getMessages()[0].getSource());
|
||||
assertEquals(Severity.ERROR, context.getMessageContext().getMessages()[0].getSeverity());
|
||||
assertEquals("The 'integerProperty' value is the wrong type", context.getMessageContext().getMessages()[0]
|
||||
.getText());
|
||||
}
|
||||
|
||||
public static class BindBean {
|
||||
private String stringProperty;
|
||||
private int integerProperty;
|
||||
|
||||
public String getStringProperty() {
|
||||
return stringProperty;
|
||||
}
|
||||
|
||||
public void setStringProperty(String stringProperty) {
|
||||
this.stringProperty = stringProperty;
|
||||
}
|
||||
|
||||
public int getIntegerProperty() {
|
||||
return integerProperty;
|
||||
}
|
||||
|
||||
public void setIntegerProperty(int integerProperty) {
|
||||
this.integerProperty = integerProperty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,7 +213,8 @@ public class FlowTests extends TestCase {
|
||||
MockRequestControlContext context = new MockRequestControlContext(flow);
|
||||
LocalAttributeMap sessionInput = new LocalAttributeMap();
|
||||
flow.start(context, sessionInput);
|
||||
assertFalse(context.getFlowScope().contains("attr"));
|
||||
assertTrue(context.getFlowScope().contains("attr"));
|
||||
assertNull(context.getFlowScope().get("attr"));
|
||||
}
|
||||
|
||||
public void testOnEventNullCurrentState() {
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.springframework.webflow.engine.builder.xml;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.beans.factory.support.StaticListableBeanFactory;
|
||||
import org.springframework.binding.mapping.RequiredMappingException;
|
||||
import org.springframework.binding.mapping.AttributeMappingException;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.webflow.action.ExternalRedirectAction;
|
||||
import org.springframework.webflow.action.FlowDefinitionRedirectAction;
|
||||
@@ -123,7 +123,7 @@ public class XmlFlowBuilderTests extends TestCase {
|
||||
execution.start(input, context);
|
||||
fail("Should have failed");
|
||||
} catch (FlowExecutionException e) {
|
||||
RequiredMappingException me = (RequiredMappingException) e.getRootCause();
|
||||
AttributeMappingException me = (AttributeMappingException) e.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ public class XmlFlowBuilderTests extends TestCase {
|
||||
execution.start(input, context);
|
||||
fail("Should have failed");
|
||||
} catch (FlowExecutionException e) {
|
||||
RequiredMappingException me = (RequiredMappingException) e.getRootCause();
|
||||
AttributeMappingException me = (AttributeMappingException) e.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user