diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/PropertyNotFoundException.java b/spring-binding/src/main/java/org/springframework/binding/expression/PropertyNotFoundException.java new file mode 100644 index 00000000..f2222e8d --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/expression/PropertyNotFoundException.java @@ -0,0 +1,18 @@ +package org.springframework.binding.expression; + +/** + * An evaluation exception indicating a expression that references a property failed to evaluate because the property + * could not be found. + * @author Keith Donald + */ +public class PropertyNotFoundException extends EvaluationException { + + /** + * Creates a new property not found exception + * @param evaluationAttempt the evaluaion attempt details + * @param cause root cause of the failure + */ + public PropertyNotFoundException(EvaluationAttempt evaluationAttempt, Throwable cause) { + super(evaluationAttempt, cause); + } +} diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/el/ELExpression.java b/spring-binding/src/main/java/org/springframework/binding/expression/el/ELExpression.java index ac3d85e5..abe129c2 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/el/ELExpression.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/el/ELExpression.java @@ -7,6 +7,7 @@ import javax.el.ValueExpression; import org.springframework.binding.expression.EvaluationAttempt; import org.springframework.binding.expression.EvaluationException; import org.springframework.binding.expression.Expression; +import org.springframework.binding.expression.PropertyNotFoundException; import org.springframework.binding.expression.SetValueAttempt; import org.springframework.util.Assert; @@ -46,8 +47,10 @@ public class ELExpression implements Expression { throw new EvaluationException(new EvaluationAttempt(this, context), null); } return result; - } catch (ELException ex) { - throw new EvaluationException(new EvaluationAttempt(this, context), ex); + } catch (javax.el.PropertyNotFoundException e) { + throw new PropertyNotFoundException(new EvaluationAttempt(this, context), e); + } catch (ELException e) { + throw new EvaluationException(new EvaluationAttempt(this, context), e); } } @@ -58,6 +61,8 @@ public class ELExpression implements Expression { if (!ctx.isPropertyResolved()) { throw new EvaluationException(new SetValueAttempt(this, context, value), null); } + } catch (javax.el.PropertyNotFoundException e) { + throw new PropertyNotFoundException(new EvaluationAttempt(this, context), e); } catch (ELException ex) { throw new EvaluationException(new EvaluationAttempt(this, context), ex); } @@ -67,6 +72,8 @@ public class ELExpression implements Expression { ELContext ctx = elContextFactory.getELContext(context); try { return valueExpression.getType(ctx); + } catch (javax.el.PropertyNotFoundException e) { + throw new PropertyNotFoundException(new EvaluationAttempt(this, context), e); } catch (ELException ex) { throw new EvaluationException(new EvaluationAttempt(this, context), ex); } diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/AttributeMapper.java b/spring-binding/src/main/java/org/springframework/binding/mapping/AttributeMapper.java deleted file mode 100644 index adeef839..00000000 --- a/spring-binding/src/main/java/org/springframework/binding/mapping/AttributeMapper.java +++ /dev/null @@ -1,36 +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; - -/** - * A lightweight service interface for mapping between two attribute sources. - *

- * Implementations of this interface are expected to encapsulate the mapping configuration information as well as the - * logic to act on it to perform mapping between a given source and target attribute source. - * - * @author Keith Donald - */ -public interface AttributeMapper { - - /** - * Map data from a source object to a target object. - * @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) throws AttributeMappingException; -} \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/AttributeMappingException.java b/spring-binding/src/main/java/org/springframework/binding/mapping/AttributeMappingException.java deleted file mode 100644 index f8d17af7..00000000 --- a/spring-binding/src/main/java/org/springframework/binding/mapping/AttributeMappingException.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.springframework.binding.mapping; - -public class AttributeMappingException extends RuntimeException { - -} diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/RequiredMappingException.java b/spring-binding/src/main/java/org/springframework/binding/mapping/Mapper.java similarity index 69% rename from spring-binding/src/main/java/org/springframework/binding/mapping/RequiredMappingException.java rename to spring-binding/src/main/java/org/springframework/binding/mapping/Mapper.java index 1cba4ce0..da1ca8c9 100644 --- a/spring-binding/src/main/java/org/springframework/binding/mapping/RequiredMappingException.java +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/Mapper.java @@ -1,32 +1,32 @@ -/* - * 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; - -/** - * Thrown when a required mapping could not be performed. - * - * @author Keith Donald - */ -public class RequiredMappingException extends IllegalStateException { - - /** - * Create a new required mapping exception. - * @param message a descriptive message - */ - public RequiredMappingException(String message) { - super(message); - } -} +/* + * 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; + +/** + * Maps state between two objects. + * + * @author Keith Donald + */ +public interface Mapper { + + /** + * Map state from a source object to a target object. + * @param source the source + * @param target the target + * @return results of the mapping transaction + */ + public MappingResults map(Object source, Object target); +} \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/MappingBuilder.java b/spring-binding/src/main/java/org/springframework/binding/mapping/MappingBuilder.java deleted file mode 100644 index cfbb9644..00000000 --- a/spring-binding/src/main/java/org/springframework/binding/mapping/MappingBuilder.java +++ /dev/null @@ -1,192 +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 org.springframework.binding.convert.ConversionService; -import org.springframework.binding.convert.ConversionExecutor; -import org.springframework.binding.convert.support.DefaultConversionService; -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.CollectionAddingExpression; -import org.springframework.util.Assert; - -/** - * A stateful builder that builds {@link Mapping} objects. Designed for convenience to build mappings in a clear, - * readable manner. - *

- * Example usage: - * - *

- * MappingBuilder mapping = new MappingBuilder();
- * Mapping result = mapping.source("foo").target("bar").from(String.class).to(Long.class).value();
- * 
- * - * Calling the {@link #value()} result method clears out this builder's state so it can be reused to build another - * mapping. - * - * @author Keith Donald - * @author Erwin Vervaet - */ -public class MappingBuilder { - - /** - * The expression string parser. - */ - private ExpressionParser expressionParser; - - /** - * The conversion service for applying type conversions. - */ - private ConversionService conversionService = new DefaultConversionService(); - - /** - * The source mapping expression. - */ - private Expression sourceExpression; - - /** - * The target mapping settable expression. - */ - private Expression targetExpression; - - /** - * The type of the object returned by evaluating the source expression. - */ - private Class sourceType; - - /** - * The type of the property settable by the target expression. - */ - private Class targetType; - - /** - * Whether or not the built mapping is a required mapping. - */ - private boolean required; - - /** - * Creates a mapping builder that uses the expression parser to parse attribute mapping expressions. - * @param expressionParser the expression parser - */ - public MappingBuilder(ExpressionParser expressionParser) { - Assert.notNull(expressionParser, "The expression parser is required"); - this.expressionParser = expressionParser; - } - - /** - * Sets the conversion service that will convert the object returned by evaluating the source expression to the - * {@link #to(Class)} type if necessary. - * @param conversionService the conversion service - */ - public void setConversionService(ConversionService conversionService) { - this.conversionService = conversionService; - } - - /** - * Sets the source expression of the mapping built by this builder. - * @param expressionString the expression string - * @return this, to support call-chaining - */ - public MappingBuilder source(String expressionString) { - sourceExpression = expressionParser.parseExpression(expressionString, null); - return this; - } - - /** - * Sets the target property expression of the mapping built by this builder. - * @param expressionString the expression string - * @return this, to support call-chaining - */ - public MappingBuilder target(String expressionString) { - targetExpression = expressionParser.parseExpression(expressionString, null); - return this; - } - - /** - * Sets the target collection of the mapping built by this builder. - * @param expressionString the expression string, resolving a collection - * @return this, to support call-chaining - */ - public MappingBuilder targetCollection(String expressionString) { - targetExpression = new CollectionAddingExpression(expressionParser.parseExpression(expressionString, null)); - return this; - } - - /** - * Sets the expected type of the object returned by evaluating the source expression. Used in conjunction with - * {@link #to(Class)} to perform a type conversion during the mapping process. - * @param sourceType the source type - * @return this, to support call-chaining - */ - public MappingBuilder from(Class sourceType) { - this.sourceType = sourceType; - return this; - } - - /** - * Sets the target type of the property writeable by the target expression. - * @param targetType the target type - * @return this, to support call-chaining - */ - public MappingBuilder to(Class targetType) { - this.targetType = targetType; - return this; - } - - /** - * Marks the mapping to be built a "required" mapping. - * @return this, to support call-chaining - */ - public MappingBuilder required() { - this.required = true; - return this; - } - - /** - * The logical GoF builder getResult method, returning a fully constructed Mapping from the configured pieces. Once - * called, the state of this builder is nulled out to support building a new mapping object again. - * @return the mapping result - */ - public Mapping value() { - Assert.notNull(sourceExpression, "The source expression must be set at a minimum"); - if (targetExpression == null) { - targetExpression = sourceExpression; - } - ConversionExecutor typeConverter = null; - if (targetType != null) { - if (sourceType != null) { - typeConverter = conversionService.getConversionExecutor(sourceType, targetType); - } else { - typeConverter = new RuntimeBindingConversionExecutor(targetType, conversionService); - } - } - Mapping result = new Mapping(sourceExpression, targetExpression, typeConverter, required); - reset(); - return result; - } - - /** - * Reset this mapping builder. - */ - public void reset() { - sourceExpression = null; - targetExpression = null; - sourceType = null; - targetType = null; - required = false; - } -} \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/MappingContext.java b/spring-binding/src/main/java/org/springframework/binding/mapping/MappingContext.java deleted file mode 100644 index d30d6daf..00000000 --- a/spring-binding/src/main/java/org/springframework/binding/mapping/MappingContext.java +++ /dev/null @@ -1,38 +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 org.springframework.binding.message.MessageContext; - -/** - * A context object with two main responsibities: - *
    - *
  1. Exposing information to a mapper to influence a mapping attempt. - *
  2. Providing operations for recording progress or errors during the mapping process. - *
- * Empty for now; subclasses may define their own custom context behavior accessible by a mapper with a downcast. - * - * @author Keith Donald - */ -public interface MappingContext { - - /** - * Returns the message context to use to record errors during the mapping process. - * @return the message context - */ - public MessageContext getMessageContext(); - -} diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/MappingContextImpl.java b/spring-binding/src/main/java/org/springframework/binding/mapping/MappingContextImpl.java deleted file mode 100644 index f53d1063..00000000 --- a/spring-binding/src/main/java/org/springframework/binding/mapping/MappingContextImpl.java +++ /dev/null @@ -1,17 +0,0 @@ -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; - } - -} diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/MappingResult.java b/spring-binding/src/main/java/org/springframework/binding/mapping/MappingResult.java new file mode 100644 index 00000000..9bbf8c1e --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/MappingResult.java @@ -0,0 +1,22 @@ +package org.springframework.binding.mapping; + +public class MappingResult { + + private Mapping mapping; + + private Result result; + + public MappingResult(Mapping mapping, Result result) { + this.mapping = mapping; + this.result = result; + } + + public Mapping getMapping() { + return mapping; + } + + public Result getResult() { + return result; + } + +} diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/MappingResults.java b/spring-binding/src/main/java/org/springframework/binding/mapping/MappingResults.java new file mode 100644 index 00000000..6d59f362 --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/MappingResults.java @@ -0,0 +1,19 @@ +package org.springframework.binding.mapping; + +import java.util.List; + +public interface MappingResults { + + public Object getSource(); + + public Object getTarget(); + + public List getAllResults(); + + public boolean hasErrorResults(); + + public List getErrorResults(); + + public List getResults(MappingResultsCriteria criteria); + +} \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/MappingResultsCriteria.java b/spring-binding/src/main/java/org/springframework/binding/mapping/MappingResultsCriteria.java new file mode 100644 index 00000000..fc46edd2 --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/MappingResultsCriteria.java @@ -0,0 +1,5 @@ +package org.springframework.binding.mapping; + +public interface MappingResultsCriteria { + public boolean test(MappingResult result); +} diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/Result.java b/spring-binding/src/main/java/org/springframework/binding/mapping/Result.java new file mode 100644 index 00000000..fcf08e00 --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/Result.java @@ -0,0 +1,11 @@ +package org.springframework.binding.mapping; + +public abstract class Result { + public abstract Object getOriginalValue(); + + public abstract Object getMappedValue(); + + public abstract boolean isError(); + + public abstract String getErrorCode(); +} diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/DefaultAttributeMapper.java b/spring-binding/src/main/java/org/springframework/binding/mapping/impl/DefaultMapper.java similarity index 61% rename from spring-binding/src/main/java/org/springframework/binding/mapping/DefaultAttributeMapper.java rename to spring-binding/src/main/java/org/springframework/binding/mapping/impl/DefaultMapper.java index c0049d1e..5e249825 100644 --- a/spring-binding/src/main/java/org/springframework/binding/mapping/DefaultAttributeMapper.java +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/impl/DefaultMapper.java @@ -13,13 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.binding.mapping; +package org.springframework.binding.mapping.impl; -import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import org.springframework.binding.convert.ConversionService; +import org.springframework.binding.mapping.Mapper; +import org.springframework.binding.mapping.Mapping; +import org.springframework.binding.mapping.MappingResults; import org.springframework.core.style.ToStringCreator; /** @@ -29,34 +32,33 @@ import org.springframework.core.style.ToStringCreator; * @author Keith Donald * @author Colin Sampaleanu */ -public class DefaultAttributeMapper implements AttributeMapper { +public class DefaultMapper implements Mapper { /** * The ordered list of mappings to apply. */ private List mappings = new LinkedList(); + private ConversionService conversionService; + + public ConversionService getConversionService() { + return conversionService; + } + + public void setConversionService(ConversionService conversionService) { + this.conversionService = conversionService; + } + /** * Add a mapping to this mapper. * @param mapping the mapping to add * @return this, to support convenient call chaining */ - public DefaultAttributeMapper addMapping(Mapping mapping) { + public DefaultMapper addMapping(DefaultMapping mapping) { mappings.add(mapping); return this; } - /** - * Add a set of mappings. - * @param mappings the mappings - */ - public void addMappings(Mapping[] mappings) { - if (mappings == null) { - return; - } - this.mappings.addAll(Arrays.asList(mappings)); - } - /** * Returns this mapper's list of mappings. * @return the list of mappings @@ -65,21 +67,14 @@ public class DefaultAttributeMapper implements AttributeMapper { return (Mapping[]) mappings.toArray(new Mapping[mappings.size()]); } - public void map(Object source, Object target, MappingContext context) throws AttributeMappingException { - boolean mappingFailure = false; - if (mappings != null) { - Iterator it = mappings.iterator(); - while (it.hasNext()) { - Mapping mapping = (Mapping) it.next(); - boolean result = mapping.map(source, target, context); - if (!result && !mappingFailure) { - mappingFailure = true; - } - } - } - if (mappingFailure) { - throw new AttributeMappingException(); + public MappingResults map(Object source, Object target) { + DefaultMappingContext context = new DefaultMappingContext(source, target, conversionService); + Iterator it = mappings.iterator(); + while (it.hasNext()) { + DefaultMapping mapping = (DefaultMapping) it.next(); + mapping.map(context); } + return context.toResult(); } public String toString() { diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/impl/DefaultMapping.java b/spring-binding/src/main/java/org/springframework/binding/mapping/impl/DefaultMapping.java new file mode 100644 index 00000000..313f9519 --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/impl/DefaultMapping.java @@ -0,0 +1,183 @@ +/* + * 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.impl; + +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.convert.ConversionService; +import org.springframework.binding.expression.EvaluationException; +import org.springframework.binding.expression.Expression; +import org.springframework.binding.mapping.Mapping; +import org.springframework.core.style.ToStringCreator; +import org.springframework.util.Assert; + +/** + * A single mapping definition, encapsulating the information necessary to map the result of evaluating an expression on + * a source object to a property on a target object, optionally applying a type conversion during the mapping process. + * + * @author Keith Donald + */ +public class DefaultMapping implements Mapping { + + private static final Log logger = LogFactory.getLog(DefaultMapping.class); + + /** + * The source expression to evaluate against a source object to map from. + */ + private final Expression sourceExpression; + + /** + * The target expression to set on a target object to map to. + */ + private final Expression targetExpression; + + /** + * Whether or not this is a required mapping; if true, the source expression must return a non-null value. + */ + private boolean required; + + /** + * A specific type conversion routine to apply during the mapping process. + */ + private ConversionExecutor typeConverter; + + /** + * Creates a new mapping. + * @param sourceExpression the source expression + * @param targetExpression the target expression + */ + public DefaultMapping(Expression sourceExpression, Expression targetExpression) { + Assert.notNull(sourceExpression, "The source expression is required"); + Assert.notNull(targetExpression, "The target expression is required"); + this.sourceExpression = sourceExpression; + this.targetExpression = targetExpression; + } + + // implementing mapping + + public Expression getSourceExpression() { + return sourceExpression; + } + + public Expression getTargetExpression() { + return targetExpression; + } + + public boolean isRequired() { + return required; + } + + // optional impl getters/setters + + public ConversionExecutor getTypeConverter() { + return typeConverter; + } + + public void setTypeConverter(ConversionExecutor typeConverter) { + this.typeConverter = typeConverter; + } + + public void setRequired(boolean required) { + this.required = required; + } + + /** + * Execute this mapping. + * @param context the mapping context + */ + public void map(MappingContext context) { + context.setCurrentMapping(this); + Object sourceValue; + try { + sourceValue = sourceExpression.getValue(context.getSource()); + } catch (EvaluationException e) { + context.setSourceAccessError(e); + return; + } + if (required && (sourceValue == null || isEmptyString(sourceValue))) { + context.setRequiredErrorResult(sourceValue); + return; + } + Object targetValue = sourceValue; + if (sourceValue != null) { + if (typeConverter != null) { + try { + targetValue = typeConverter.execute(targetValue); + } catch (ConversionException e) { + context.setTypeConversionErrorResult(sourceValue, e.getTargetClass()); + return; + } + } else { + ConversionService conversionService = context.getConversionService(); + if (conversionService != null) { + Class targetType; + try { + targetType = targetExpression.getValueType(context.getTarget()); + } catch (EvaluationException e) { + context.setTargetAccessError(sourceValue, e); + return; + } + if (targetType != null && !targetType.isInstance(targetValue)) { + try { + ConversionExecutor typeConverter = conversionService.getConversionExecutor(sourceValue + .getClass(), targetType); + targetValue = typeConverter.execute(sourceValue); + } catch (ConversionException e) { + context.setTypeConversionErrorResult(sourceValue, e.getTargetClass()); + return; + } + } + } + } + } + if (logger.isDebugEnabled()) { + logger.debug("Mapping '" + sourceExpression + "' value [" + sourceValue + "] to target '" + + targetExpression + "'; setting target value to [" + targetValue + "]"); + } + try { + targetExpression.setValue(context.getTarget(), targetValue); + context.setSuccessResult(sourceValue, targetValue); + } catch (EvaluationException e) { + context.setTargetAccessError(sourceValue, e); + } + } + + private boolean isEmptyString(Object sourceValue) { + if (sourceValue instanceof CharSequence) { + return ((CharSequence) sourceValue).length() == 0; + } else { + return false; + } + } + + public boolean equals(Object o) { + if (!(o instanceof DefaultMapping)) { + return false; + } + DefaultMapping other = (DefaultMapping) o; + return sourceExpression.equals(other.sourceExpression) && targetExpression.equals(other.targetExpression); + } + + public int hashCode() { + return sourceExpression.hashCode() + targetExpression.hashCode(); + } + + public String toString() { + return new ToStringCreator(this).append(sourceExpression + " -> " + targetExpression).toString(); + } +} \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/impl/DefaultMappingContext.java b/spring-binding/src/main/java/org/springframework/binding/mapping/impl/DefaultMappingContext.java new file mode 100644 index 00000000..b6a5e19b --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/impl/DefaultMappingContext.java @@ -0,0 +1,83 @@ +package org.springframework.binding.mapping.impl; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.binding.convert.ConversionService; +import org.springframework.binding.expression.EvaluationException; +import org.springframework.binding.mapping.Mapping; +import org.springframework.binding.mapping.MappingResult; +import org.springframework.binding.mapping.MappingResults; +import org.springframework.binding.mapping.results.RequiredError; +import org.springframework.binding.mapping.results.SourceAccessError; +import org.springframework.binding.mapping.results.Success; +import org.springframework.binding.mapping.results.TargetAccessError; +import org.springframework.binding.mapping.results.TypeConversionError; + +class DefaultMappingContext implements MappingContext { + + private Object source; + + private Object target; + + private Mapping currentMapping; + + private List mappingResults; + + private ConversionService conversionService; + + public DefaultMappingContext(Object source, Object target, ConversionService conversionService) { + this.source = source; + this.target = target; + this.conversionService = conversionService; + this.mappingResults = new ArrayList(); + } + + public Object getSource() { + return source; + } + + public Object getTarget() { + return target; + } + + public ConversionService getConversionService() { + return conversionService; + } + + public void setCurrentMapping(Mapping mapping) { + if (this.currentMapping != null) { + throw new IllegalStateException("The current mapping has not finished yet"); + } + this.currentMapping = mapping; + } + + public void setSuccessResult(Object originalValue, Object mappedValue) { + mappingResults.add(new MappingResult(currentMapping, new Success(originalValue, mappedValue))); + currentMapping = null; + } + + public void setRequiredErrorResult(Object originalValue) { + mappingResults.add(new MappingResult(currentMapping, new RequiredError(originalValue))); + this.currentMapping = null; + } + + public void setTypeConversionErrorResult(Object originalValue, Class targetType) { + mappingResults.add(new MappingResult(currentMapping, new TypeConversionError(originalValue, targetType))); + this.currentMapping = null; + } + + public void setSourceAccessError(EvaluationException error) { + mappingResults.add(new MappingResult(currentMapping, new SourceAccessError(error))); + this.currentMapping = null; + } + + public void setTargetAccessError(Object originalValue, EvaluationException error) { + mappingResults.add(new MappingResult(currentMapping, new TargetAccessError(originalValue, error))); + this.currentMapping = null; + } + + public MappingResults toResult() { + return new DefaultMappingResults(source, target, mappingResults); + } +} diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/impl/DefaultMappingResults.java b/spring-binding/src/main/java/org/springframework/binding/mapping/impl/DefaultMappingResults.java new file mode 100644 index 00000000..bae9be94 --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/impl/DefaultMappingResults.java @@ -0,0 +1,73 @@ +package org.springframework.binding.mapping.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.springframework.binding.mapping.MappingResult; +import org.springframework.binding.mapping.MappingResults; +import org.springframework.binding.mapping.MappingResultsCriteria; + +public class DefaultMappingResults implements MappingResults { + + private Object source; + + private Object target; + + private List mappingResults; + + public DefaultMappingResults(Object source, Object target, List mappingResults) { + this.source = source; + this.target = target; + this.mappingResults = mappingResults; + } + + public Object getSource() { + return source; + } + + public Object getTarget() { + return target; + } + + public List getAllResults() { + return Collections.unmodifiableList(mappingResults); + } + + public boolean hasErrorResults() { + Iterator it = mappingResults.iterator(); + while (it.hasNext()) { + MappingResult result = (MappingResult) it.next(); + if (result.getResult().isError()) { + return true; + } + } + return false; + } + + public List getErrorResults() { + List errorResults = new ArrayList(); + Iterator it = mappingResults.iterator(); + while (it.hasNext()) { + MappingResult result = (MappingResult) it.next(); + if (result.getResult().isError()) { + errorResults.add(result); + } + } + return Collections.unmodifiableList(errorResults); + } + + public List getResults(MappingResultsCriteria criteria) { + List results = new ArrayList(); + Iterator it = mappingResults.iterator(); + while (it.hasNext()) { + MappingResult result = (MappingResult) it.next(); + if (criteria.test(result)) { + results.add(result); + } + } + return Collections.unmodifiableList(results); + } + +} diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/impl/MappingContext.java b/spring-binding/src/main/java/org/springframework/binding/mapping/impl/MappingContext.java new file mode 100644 index 00000000..0d0f0b5e --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/impl/MappingContext.java @@ -0,0 +1,87 @@ +/* + * 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.impl; + +import org.springframework.binding.convert.ConversionService; +import org.springframework.binding.expression.EvaluationException; +import org.springframework.binding.mapping.Mapping; + +/** + * A context for a mapping transaction. + * + * @author Keith Donald + */ +public interface MappingContext { + + /** + * The object being mapped from. + */ + public Object getSource(); + + /** + * The object being mapped to. + */ + public Object getTarget(); + + /** + * Returns the conversion service that can be used to perform type conversions during the mapping process. May be + * null if no externally managed conversion service is provided. + */ + public ConversionService getConversionService(); + + /** + * Sets the current mapping. Called when a single mapping operation is about to begin. This updates progress of the + * overall mapping transaction. + * @param mapping the mapping to make the current mapping + */ + public void setCurrentMapping(Mapping mapping); + + /** + * Indicates the current mapping completed successfully. + * @param originalValue the original value from the source of the mapping + * @param mappedValue the successfully mapped value, which may be different from the original if a type conversion + * was performed + */ + public void setSuccessResult(Object originalValue, Object mappedValue); + + /** + * Indicates the current mapping ended with a 'required' error. This means the value obtained from the source was + * empty, and the mapping could not be completed as a result. + * @param originalValue the original source value that is empty (null or an empty string, typically) + */ + public void setRequiredErrorResult(Object originalValue); + + /** + * Indicates the current mapping ended with a 'type conversion' error. This means the value obtained from the source + * could not be converted to a type that could be assigned to the target expression. + * @param originalValue the original source value that could not be converted during the mapping attempt + * @param targetType the desired target type to which conversion could not be performed + */ + public void setTypeConversionErrorResult(Object originalValue, Class targetType); + + /** + * Indicates a error occurred accessing the source mapping expression. + * @param error the error that occurred + */ + public void setSourceAccessError(EvaluationException error); + + /** + * Indicates a error occurred accessing the target mapping expression. + * @param error the error that occurred + */ + public void setTargetAccessError(Object originalValue, EvaluationException error); + +} diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/results/RequiredError.java b/spring-binding/src/main/java/org/springframework/binding/mapping/results/RequiredError.java new file mode 100644 index 00000000..cf4e03ba --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/results/RequiredError.java @@ -0,0 +1,28 @@ +package org.springframework.binding.mapping.results; + +import org.springframework.binding.mapping.Result; + +public class RequiredError extends Result { + + private Object originalValue; + + public RequiredError(Object originalValue) { + this.originalValue = originalValue; + } + + public Object getOriginalValue() { + return originalValue; + } + + public Object getMappedValue() { + return null; + } + + public boolean isError() { + return true; + } + + public String getErrorCode() { + return "required"; + } +} diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/results/SourceAccessError.java b/spring-binding/src/main/java/org/springframework/binding/mapping/results/SourceAccessError.java new file mode 100644 index 00000000..ac7c3f35 --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/results/SourceAccessError.java @@ -0,0 +1,34 @@ +package org.springframework.binding.mapping.results; + +import org.springframework.binding.expression.EvaluationException; +import org.springframework.binding.mapping.Result; + +public class SourceAccessError extends Result { + + private EvaluationException error; + + public SourceAccessError(EvaluationException error) { + this.error = error; + } + + public EvaluationException getException() { + return error; + } + + public Object getOriginalValue() { + return null; + } + + public Object getMappedValue() { + return null; + } + + public boolean isError() { + return true; + } + + public String getErrorCode() { + return "sourceAccess"; + } + +} diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/results/Success.java b/spring-binding/src/main/java/org/springframework/binding/mapping/results/Success.java new file mode 100644 index 00000000..4a7e004f --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/results/Success.java @@ -0,0 +1,30 @@ +package org.springframework.binding.mapping.results; + +import org.springframework.binding.mapping.Result; + +public class Success extends Result { + + private Object originalValue; + private Object mappedValue; + + public Success(Object mappedValue, Object originalValue) { + this.mappedValue = mappedValue; + this.originalValue = originalValue; + } + + public Object getOriginalValue() { + return originalValue; + } + + public Object getMappedValue() { + return mappedValue; + } + + public boolean isError() { + return false; + } + + public String getErrorCode() { + return null; + } +} diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/results/TargetAccessError.java b/spring-binding/src/main/java/org/springframework/binding/mapping/results/TargetAccessError.java new file mode 100644 index 00000000..d4602c9b --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/results/TargetAccessError.java @@ -0,0 +1,41 @@ +package org.springframework.binding.mapping.results; + +import org.springframework.binding.expression.EvaluationException; +import org.springframework.binding.expression.PropertyNotFoundException; +import org.springframework.binding.mapping.Result; + +public class TargetAccessError extends Result { + + private Object originalValue; + + private EvaluationException error; + + public TargetAccessError(Object originalValue, EvaluationException error) { + this.originalValue = originalValue; + this.error = error; + } + + public EvaluationException getException() { + return error; + } + + public Object getOriginalValue() { + return originalValue; + } + + public Object getMappedValue() { + return null; + } + + public boolean isError() { + return true; + } + + public String getErrorCode() { + if (error instanceof PropertyNotFoundException) { + return "propertyNotFound"; + } else { + return "targetAccess"; + } + } +} diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/results/TypeConversionError.java b/spring-binding/src/main/java/org/springframework/binding/mapping/results/TypeConversionError.java new file mode 100644 index 00000000..8ccbde51 --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/results/TypeConversionError.java @@ -0,0 +1,36 @@ +package org.springframework.binding.mapping.results; + +import org.springframework.binding.mapping.Result; + +public class TypeConversionError extends Result { + + private Object originalValue; + + private Class targetType; + + public TypeConversionError(Object originalValue, Class targetType) { + this.originalValue = originalValue; + this.targetType = targetType; + } + + public Object getOriginalValue() { + return originalValue; + } + + public Class getTargetType() { + return targetType; + } + + public Object getMappedValue() { + return null; + } + + public boolean isError() { + return true; + } + + public String getErrorCode() { + return "typeMismatch"; + } + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/action/AttributeMapperAction.java b/spring-webflow/src/main/java/org/springframework/webflow/action/AttributeMapperAction.java deleted file mode 100644 index fe60e204..00000000 --- a/spring-webflow/src/main/java/org/springframework/webflow/action/AttributeMapperAction.java +++ /dev/null @@ -1,69 +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.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; - -/** - * Action that executes an attribute mapper to map information in the request context. Both the source and the target of - * the mapping will be the request context. This allows for maximum flexibility when defining attribute mapping - * expressions (e.g. "${flowScope.someAttribute}"). - *

- * This action always returns the {@link org.springframework.webflow.action.AbstractAction#success() success} event. If - * something goes wrong while executing the mapping, an exception is thrown. - * - * @see org.springframework.binding.mapping.AttributeMapper - * @see org.springframework.webflow.execution.RequestContext - * - * @author Keith Donald - * @author Erwin Vervaet - */ -public class AttributeMapperAction extends AbstractAction { - - /** - * The attribute mapper strategy to delegate to perform the mapping. - */ - private AttributeMapper attributeMapper; - - /** - * Creates a new attribute mapper action that delegates to the configured attribute mapper to complete the mapping - * process. - * @param attributeMapper the mapper - */ - public AttributeMapperAction(AttributeMapper attributeMapper) { - Assert.notNull(attributeMapper, "The attribute mapper is required"); - this.attributeMapper = attributeMapper; - } - - protected Event doExecute(RequestContext context) throws Exception { - // map attributes from and to the request context - attributeMapper.map(context, context, getMappingContext(context)); - return success(); - } - - /** - * Returns a context containing extra data available during attribute mapping. The default implementation just - * returns null. Subclasses can override this if necessary. - */ - protected MappingContext getMappingContext(RequestContext context) { - return new MappingContextImpl(context.getMessageContext()); - } -} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/action/BindAction.java b/spring-webflow/src/main/java/org/springframework/webflow/action/BindAction.java index a84cb555..2a0b31b9 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/action/BindAction.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/action/BindAction.java @@ -5,22 +5,24 @@ import java.util.Iterator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.binding.convert.ConversionService; -import org.springframework.binding.convert.support.RuntimeBindingConversionExecutor; -import org.springframework.binding.expression.EvaluationException; 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.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.webflow.core.collection.AttributeMap; import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.RequestContext; public class BindAction extends AbstractAction { - private static Log logger = LogFactory.getLog(BindAction.class); + private static final Log logger = LogFactory.getLog(BindAction.class); + + private static final MappingResultsCriteria PROPERTY_NOT_FOUND_ERRORS = new PropertyNotFoundErrors(); private Expression target; @@ -40,7 +42,8 @@ public class BindAction extends AbstractAction { throw new IllegalStateException( "The bind target cannot be null - check your expression. Bind target expression = " + target); } - DefaultAttributeMapper mapper = new DefaultAttributeMapper(); + DefaultMapper mapper = new DefaultMapper(); + mapper.setConversionService(conversionService); AttributeMap eventAttributes = context.getLastEvent().getAttributes(); if (logger.isDebugEnabled()) { logger.debug("Binding event '" + context.getLastEvent().getId() + "' attributes " + eventAttributes @@ -52,23 +55,25 @@ public class BindAction extends AbstractAction { .eval(AttributeMap.class)); Expression targetAttribute = expressionParser.parseExpression(name, new ParserContextImpl().eval(target .getClass())); - Class targetType; - try { - targetType = targetAttribute.getValueType(target); - } catch (EvaluationException e) { - targetType = null; - } - if (targetType != null) { - mapper.addMapping(new Mapping(sourceAttribute, targetAttribute, new RuntimeBindingConversionExecutor( - targetType, conversionService), false)); + mapper.addMapping(new DefaultMapping(sourceAttribute, targetAttribute)); + } + MappingResults results = mapper.map(context.getLastEvent().getAttributes(), target); + if (!results.hasErrorResults()) { + return success(); + } else { + if (results.getResults(PROPERTY_NOT_FOUND_ERRORS).size() == results.getErrorResults().size()) { + // all errors are 'property not found' -- acceptable + return success(); + } else { + return error(); } } - try { - mapper.map(context.getLastEvent().getAttributes(), target, new MappingContextImpl(context - .getMessageContext())); - return success(); - } catch (AttributeMappingException e) { - return error(); + } + + private static class PropertyNotFoundErrors implements MappingResultsCriteria { + public boolean test(MappingResult result) { + return result.getResult() instanceof TargetAccessError + && result.getResult().getErrorCode().equals("propertyNotFound"); } } } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/EndState.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/EndState.java index 434d9c87..f8305b52 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/EndState.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/EndState.java @@ -15,8 +15,8 @@ */ package org.springframework.webflow.engine; -import org.springframework.binding.mapping.AttributeMapper; -import org.springframework.binding.mapping.MappingContextImpl; +import org.springframework.binding.mapping.Mapper; +import org.springframework.binding.mapping.MappingResults; import org.springframework.core.style.ToStringCreator; import org.springframework.webflow.core.collection.LocalAttributeMap; import org.springframework.webflow.execution.Action; @@ -53,7 +53,7 @@ public class EndState extends State { /** * The attribute mapper for mapping output attributes exposed by this end state when it is entered. */ - private AttributeMapper outputMapper; + private Mapper outputMapper; /** * Create a new end state with no associated view. @@ -62,7 +62,7 @@ public class EndState extends State { * @throws IllegalArgumentException when this state cannot be added to given flow, e.g. because the id is not unique * @see State#State(Flow, String) * @see #setFinalResponseAction(Action) - * @see #setOutputMapper(AttributeMapper) + * @see #setOutputMapper(Mapper) */ public EndState(Flow flow, String id) throws IllegalArgumentException { super(flow, id); @@ -78,7 +78,7 @@ 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) { + public void setOutputMapper(Mapper outputMapper) { this.outputMapper = outputMapper; } @@ -113,11 +113,14 @@ public class EndState extends State { * execution request context into a newly created empty map. */ protected LocalAttributeMap createSessionOutput(RequestContext context) { - LocalAttributeMap outputMap = new LocalAttributeMap(); + LocalAttributeMap output = new LocalAttributeMap(); if (outputMapper != null) { - outputMapper.map(context, outputMap, new MappingContextImpl(context.getMessageContext())); + MappingResults results = outputMapper.map(context, output); + if (results != null && results.hasErrorResults()) { + throw new FlowOutputMappingException(getOwner().getId(), getId(), results); + } } - return outputMap; + return output; } protected void appendToString(ToStringCreator creator) { diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/Flow.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/Flow.java index c7c410af..6d4aa7d7 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/Flow.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/Flow.java @@ -25,8 +25,8 @@ import org.apache.commons.logging.Log; 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.binding.mapping.Mapper; +import org.springframework.binding.mapping.MappingResults; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.ResourceLoader; import org.springframework.core.style.StylerUtils; @@ -137,7 +137,7 @@ public class Flow extends AnnotatedObject implements FlowDefinition { /** * The mapper to map flow input attributes. */ - private AttributeMapper inputMapper; + private Mapper inputMapper; /** * The list of actions to execute when this flow starts. @@ -160,7 +160,7 @@ public class Flow extends AnnotatedObject implements FlowDefinition { /** * The mapper to map flow output attributes. */ - private AttributeMapper outputMapper; + private Mapper outputMapper; /** * The set of exception handlers for this flow. @@ -388,7 +388,7 @@ public class Flow extends AnnotatedObject implements FlowDefinition { * Returns the configured flow input mapper, or null if none. * @return the input mapper */ - public AttributeMapper getInputMapper() { + public Mapper getInputMapper() { return inputMapper; } @@ -396,7 +396,7 @@ public class Flow extends AnnotatedObject implements FlowDefinition { * Sets the mapper to map flow input attributes. * @param inputMapper the input mapper */ - public void setInputMapper(AttributeMapper inputMapper) { + public void setInputMapper(Mapper inputMapper) { this.inputMapper = inputMapper; } @@ -422,7 +422,7 @@ public class Flow extends AnnotatedObject implements FlowDefinition { * Returns the configured flow output mapper, or null if none. * @return the output mapper */ - public AttributeMapper getOutputMapper() { + public Mapper getOutputMapper() { return outputMapper; } @@ -430,7 +430,7 @@ public class Flow extends AnnotatedObject implements FlowDefinition { * Sets the mapper to map flow output attributes. * @param outputMapper the output mapper */ - public void setOutputMapper(AttributeMapper outputMapper) { + public void setOutputMapper(Mapper outputMapper) { this.outputMapper = outputMapper; } @@ -500,7 +500,7 @@ public class Flow extends AnnotatedObject implements FlowDefinition { *

    *
  1. Create (setup) all registered flow variables ({@link #addVariable(FlowVariable)}) in flow scope.
  2. *
  3. Map provided input data into the flow. Typically data will be mapped into flow scope using the registered - * input mapper ({@link #setInputMapper(AttributeMapper)}).
  4. + * input mapper ({@link #setInputMapper(Mapper)}). *
  5. Execute all registered start actions ({@link #getStartActionList()}).
  6. *
  7. Enter the configured start state ({@link #setStartState(State)})
  8. *
@@ -512,7 +512,10 @@ public class Flow extends AnnotatedObject implements FlowDefinition { assertStartStateSet(); createVariables(context); if (inputMapper != null) { - inputMapper.map(input, context, new MappingContextImpl(context.getMessageContext())); + MappingResults results = inputMapper.map(input, context); + if (results != null && results.hasErrorResults()) { + throw new FlowInputMappingException(getId(), results); + } } startActionList.execute(context); startState.enter(context); @@ -555,7 +558,7 @@ public class Flow extends AnnotatedObject implements FlowDefinition { *
    *
  1. Execute all registered end actions ({@link #getEndActionList()}).
  2. *
  3. Map data available in the flow execution control context into provided output map using a registered output - * mapper ({@link #setOutputMapper(AttributeMapper)}).
  4. + * mapper ({@link #setOutputMapper(Mapper)}). *
* @param context the flow execution control context * @param output initial output produced by the session that is eligible for modification by this method @@ -564,7 +567,10 @@ 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, new MappingContextImpl(context.getMessageContext())); + MappingResults results = outputMapper.map(context, output); + if (results != null && results.hasErrorResults()) { + throw new FlowOutputMappingException(getId(), results); + } } } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/FlowAttributeMappingException.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/FlowAttributeMappingException.java new file mode 100644 index 00000000..1b12d36b --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/FlowAttributeMappingException.java @@ -0,0 +1,18 @@ +package org.springframework.webflow.engine; + +import org.springframework.binding.mapping.MappingResults; +import org.springframework.webflow.execution.FlowExecutionException; + +public class FlowAttributeMappingException extends FlowExecutionException { + + private MappingResults results; + + public FlowAttributeMappingException(String flowId, String stateId, MappingResults results, String message) { + super(flowId, stateId, message); + this.results = results; + } + + public MappingResults getMappingResults() { + return results; + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/FlowInputMappingException.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/FlowInputMappingException.java new file mode 100644 index 00000000..b9700198 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/FlowInputMappingException.java @@ -0,0 +1,16 @@ +package org.springframework.webflow.engine; + +import org.springframework.binding.mapping.MappingResults; +import org.springframework.core.style.StylerUtils; + +public class FlowInputMappingException extends FlowAttributeMappingException { + public FlowInputMappingException(String flowId, MappingResults results) { + super(flowId, null, results, "Errors occured during flow input mapping; errors = " + + StylerUtils.style(results.getErrorResults())); + } + + public FlowInputMappingException(String flowId, String stateId, MappingResults results) { + super(flowId, stateId, results, "Errors occured during flow input mapping; errors = " + + StylerUtils.style(results.getErrorResults())); + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/FlowOutputMappingException.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/FlowOutputMappingException.java new file mode 100644 index 00000000..cb3dc9ba --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/FlowOutputMappingException.java @@ -0,0 +1,15 @@ +package org.springframework.webflow.engine; + +import org.springframework.binding.mapping.MappingResults; +import org.springframework.core.style.StylerUtils; + +public class FlowOutputMappingException extends FlowAttributeMappingException { + public FlowOutputMappingException(String flowId, MappingResults results) { + this(flowId, null, results); + } + + public FlowOutputMappingException(String flowId, String stateId, MappingResults results) { + super(flowId, stateId, results, "Errors occured during flow output mapping; errors = " + + StylerUtils.style(results.getErrorResults())); + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/SubflowAttributeMapper.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/SubflowAttributeMapper.java index 7497cdf1..aa736200 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/SubflowAttributeMapper.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/SubflowAttributeMapper.java @@ -15,12 +15,12 @@ public interface SubflowAttributeMapper { * @param context the current request execution context * @return a map of attributes to pass as input */ - public MutableAttributeMap createFlowInput(RequestContext context); + public MutableAttributeMap createSubflowInput(RequestContext context); /** * Map output attributes of an ended subflow flow to the resuming parent flow. - * @param flowOutput the output attributes returned by the ended subflow + * @param output the output attributes returned by the ended subflow * @param context the current request execution context, which gives access to the parent flow scope */ - public void mapFlowOutput(AttributeMap flowOutput, RequestContext context); + public void mapSubflowOutput(AttributeMap output, RequestContext context); } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/SubflowState.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/SubflowState.java index da2aff0c..fa33ba6b 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/SubflowState.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/SubflowState.java @@ -90,7 +90,7 @@ public class SubflowState extends TransitionableState { protected void doEnter(RequestControlContext context) throws FlowExecutionException { MutableAttributeMap flowInput; if (subflowAttributeMapper != null) { - flowInput = subflowAttributeMapper.createFlowInput(context); + flowInput = subflowAttributeMapper.createSubflowInput(context); } else { flowInput = new LocalAttributeMap(); } @@ -111,7 +111,7 @@ public class SubflowState extends TransitionableState { if (logger.isDebugEnabled()) { logger.debug("Mapping subflow output " + subflowOutput); } - subflowAttributeMapper.mapFlowOutput(subflowOutput, context); + subflowAttributeMapper.mapSubflowOutput(subflowOutput, context); } return super.handleEvent(context); } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/FlowArtifactFactory.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/FlowArtifactFactory.java index f1cbcaa4..b5d738a9 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/FlowArtifactFactory.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/FlowArtifactFactory.java @@ -16,7 +16,7 @@ package org.springframework.webflow.engine.builder; import org.springframework.binding.expression.Expression; -import org.springframework.binding.mapping.AttributeMapper; +import org.springframework.binding.mapping.Mapper; import org.springframework.webflow.core.collection.AttributeMap; import org.springframework.webflow.engine.ActionState; import org.springframework.webflow.engine.DecisionState; @@ -179,7 +179,7 @@ public class FlowArtifactFactory { * @return the fully initialized subflow state instance */ public State createEndState(String id, Flow flow, Action[] entryActions, Action finalResponseAction, - AttributeMapper outputMapper, FlowExecutionExceptionHandler[] exceptionHandlers, AttributeMap attributes) { + Mapper outputMapper, FlowExecutionExceptionHandler[] exceptionHandlers, AttributeMap attributes) { EndState endState = new EndState(flow, id); if (finalResponseAction != null) { endState.setFinalResponseAction(finalResponseAction); diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilder.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilder.java index d08bbc3d..8ff3f899 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilder.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilder.java @@ -39,9 +39,9 @@ import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.ExpressionParser; import org.springframework.binding.expression.ParserContext; import org.springframework.binding.expression.support.ParserContextImpl; -import org.springframework.binding.mapping.AttributeMapper; -import org.springframework.binding.mapping.DefaultAttributeMapper; -import org.springframework.binding.mapping.Mapping; +import org.springframework.binding.mapping.Mapper; +import org.springframework.binding.mapping.impl.DefaultMapper; +import org.springframework.binding.mapping.impl.DefaultMapping; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.support.GenericApplicationContext; @@ -194,7 +194,7 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde } public void buildInputMapper() throws FlowBuilderException { - AttributeMapper inputMapper = parseFlowInputMapper(getDocumentElement()); + Mapper inputMapper = parseFlowInputMapper(getDocumentElement()); if (inputMapper != null) { getFlow().setInputMapper(inputMapper); } @@ -217,7 +217,7 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde } public void buildOutputMapper() throws FlowBuilderException { - AttributeMapper outputMapper = parseFlowOutputMapper(getDocumentElement()); + Mapper outputMapper = parseFlowOutputMapper(getDocumentElement()); if (outputMapper != null) { getFlow().setOutputMapper(outputMapper); } @@ -376,19 +376,19 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde } } - private AttributeMapper parseFlowInputMapper(Element element) { + private Mapper parseFlowInputMapper(Element element) { Collection inputs = DomUtils.getChildElementsByTagName(element, "input"); if (inputs.size() == 0) { return null; } - DefaultAttributeMapper inputMapper = new DefaultAttributeMapper(); + DefaultMapper inputMapper = new DefaultMapper(); for (Iterator it = inputs.iterator(); it.hasNext();) { inputMapper.addMapping(parseFlowInputMapping((Element) it.next())); } return inputMapper; } - private Mapping parseFlowInputMapping(Element element) { + private DefaultMapping parseFlowInputMapping(Element element) { ExpressionParser parser = getLocalContext().getExpressionParser(); String name = element.getAttribute("name"); String value = null; @@ -399,22 +399,25 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde } Expression source = parser.parseExpression(name, new ParserContextImpl().eval(MutableAttributeMap.class)); Expression target = parser.parseExpression(value, new ParserContextImpl().eval(RequestContext.class)); - return new Mapping(source, target, parseMappingConversionExecutor(element), parseMappingRequired(element)); + DefaultMapping mapping = new DefaultMapping(source, target); + parseAndSetMappingTypeConverter(element, mapping); + parseAndSetMappingRequired(element, mapping); + return mapping; } - private AttributeMapper parseFlowOutputMapper(Element element) { + private Mapper parseFlowOutputMapper(Element element) { Collection inputs = DomUtils.getChildElementsByTagName(element, "output"); if (inputs.size() == 0) { return null; } - DefaultAttributeMapper outputMapper = new DefaultAttributeMapper(); + DefaultMapper outputMapper = new DefaultMapper(); for (Iterator it = inputs.iterator(); it.hasNext();) { outputMapper.addMapping(parseFlowOutputMapping((Element) it.next())); } return outputMapper; } - private Mapping parseFlowOutputMapping(Element element) { + private DefaultMapping parseFlowOutputMapping(Element element) { ExpressionParser parser = getLocalContext().getExpressionParser(); String name = element.getAttribute("name"); String value = null; @@ -425,23 +428,25 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde } Expression source = parser.parseExpression(value, new ParserContextImpl().eval(RequestContext.class)); Expression target = parser.parseExpression(name, new ParserContextImpl().eval(MutableAttributeMap.class)); - return new Mapping(source, target, parseMappingConversionExecutor(element), parseMappingRequired(element)); + DefaultMapping mapping = new DefaultMapping(source, target); + parseAndSetMappingTypeConverter(element, mapping); + parseAndSetMappingRequired(element, mapping); + return mapping; } - private ConversionExecutor parseMappingConversionExecutor(Element element) { + private void parseAndSetMappingTypeConverter(Element element, DefaultMapping mapping) { if (element.hasAttribute("type")) { Class type = (Class) fromStringTo(Class.class).execute(element.getAttribute("type")); - return new RuntimeBindingConversionExecutor(type, getConversionService()); - } else { - return null; + ConversionExecutor typeConverter = new RuntimeBindingConversionExecutor(type, getConversionService()); + mapping.setTypeConverter(typeConverter); } } - private boolean parseMappingRequired(Element element) { + private void parseAndSetMappingRequired(Element element, DefaultMapping mapping) { if (element.hasAttribute("required")) { - return ((Boolean) fromStringTo(Boolean.class).execute(element.getAttribute("required"))).booleanValue(); - } else { - return false; + boolean required = ((Boolean) fromStringTo(Boolean.class).execute(element.getAttribute("required"))) + .booleanValue(); + mapping.setRequired(required); } } @@ -810,25 +815,25 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde return (SubflowAttributeMapper) getLocalContext().getBeanFactory().getBean(attributeMapperBeanId, SubflowAttributeMapper.class); } else { - AttributeMapper inputMapper = parseSubflowInputMapper(element); - AttributeMapper outputMapper = parseSubflowOutputMapper(element); + Mapper inputMapper = parseSubflowInputMapper(element); + Mapper outputMapper = parseSubflowOutputMapper(element); return new GenericSubflowAttributeMapper(inputMapper, outputMapper); } } - private AttributeMapper parseSubflowInputMapper(Element element) { + private Mapper parseSubflowInputMapper(Element element) { Collection inputs = DomUtils.getChildElementsByTagName(element, "input"); if (inputs.size() == 0) { return null; } - DefaultAttributeMapper inputMapper = new DefaultAttributeMapper(); + DefaultMapper inputMapper = new DefaultMapper(); for (Iterator it = inputs.iterator(); it.hasNext();) { inputMapper.addMapping(parseSubflowInputMapping((Element) it.next())); } return inputMapper; } - private Mapping parseSubflowInputMapping(Element element) { + private DefaultMapping parseSubflowInputMapping(Element element) { ExpressionParser parser = getLocalContext().getExpressionParser(); String name = element.getAttribute("name"); String value = null; @@ -839,22 +844,25 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde } Expression source = parser.parseExpression(value, new ParserContextImpl().eval(RequestContext.class)); Expression target = parser.parseExpression(name, new ParserContextImpl().eval(MutableAttributeMap.class)); - return new Mapping(source, target, parseMappingConversionExecutor(element), parseMappingRequired(element)); + DefaultMapping mapping = new DefaultMapping(source, target); + parseAndSetMappingTypeConverter(element, mapping); + parseAndSetMappingRequired(element, mapping); + return mapping; } - private AttributeMapper parseSubflowOutputMapper(Element element) { + private Mapper parseSubflowOutputMapper(Element element) { Collection inputs = DomUtils.getChildElementsByTagName(element, "output"); if (inputs.size() == 0) { return null; } - DefaultAttributeMapper outputMapper = new DefaultAttributeMapper(); + DefaultMapper outputMapper = new DefaultMapper(); for (Iterator it = inputs.iterator(); it.hasNext();) { outputMapper.addMapping(parseSubflowOutputMapping((Element) it.next())); } return outputMapper; } - private Mapping parseSubflowOutputMapping(Element element) { + private DefaultMapping parseSubflowOutputMapping(Element element) { ExpressionParser parser = getLocalContext().getExpressionParser(); String name = element.getAttribute("name"); String value = null; @@ -865,7 +873,10 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde } Expression source = parser.parseExpression(name, new ParserContextImpl().eval(MutableAttributeMap.class)); Expression target = parser.parseExpression(value, new ParserContextImpl().eval(RequestContext.class)); - return new Mapping(source, target, parseMappingConversionExecutor(element), parseMappingRequired(element)); + DefaultMapping mapping = new DefaultMapping(source, target); + parseAndSetMappingTypeConverter(element, mapping); + parseAndSetMappingRequired(element, mapping); + return mapping; } private FlowExecutionExceptionHandler[] parseExceptionHandlers(Element element) { diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/support/GenericSubflowAttributeMapper.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/support/GenericSubflowAttributeMapper.java index 3bf3bc20..fafe62c7 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/support/GenericSubflowAttributeMapper.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/support/GenericSubflowAttributeMapper.java @@ -17,12 +17,14 @@ 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.binding.mapping.Mapper; +import org.springframework.binding.mapping.MappingResults; 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.FlowInputMappingException; +import org.springframework.webflow.engine.FlowOutputMappingException; import org.springframework.webflow.engine.SubflowAttributeMapper; import org.springframework.webflow.execution.RequestContext; @@ -33,33 +35,41 @@ import org.springframework.webflow.execution.RequestContext; */ public final class GenericSubflowAttributeMapper implements SubflowAttributeMapper, Serializable { - private final AttributeMapper inputMapper; + private final Mapper inputMapper; - private final AttributeMapper outputMapper; + private final Mapper outputMapper; /** * Create a new flow attribute mapper using given mapping strategies. * @param inputMapper the input mapping strategy * @param outputMapper the output mapping strategy */ - public GenericSubflowAttributeMapper(AttributeMapper inputMapper, AttributeMapper outputMapper) { + public GenericSubflowAttributeMapper(Mapper inputMapper, Mapper outputMapper) { this.inputMapper = inputMapper; this.outputMapper = outputMapper; } - public MutableAttributeMap createFlowInput(RequestContext context) { + public MutableAttributeMap createSubflowInput(RequestContext context) { if (inputMapper != null) { LocalAttributeMap input = new LocalAttributeMap(); - inputMapper.map(context, input, new MappingContextImpl(context.getMessageContext())); + MappingResults results = inputMapper.map(context, input); + if (results != null && results.hasErrorResults()) { + throw new FlowInputMappingException(context.getActiveFlow().getId(), context.getCurrentState().getId(), + results); + } return input; } else { return new LocalAttributeMap(); } } - public void mapFlowOutput(AttributeMap subflowOutput, RequestContext context) { - if (outputMapper != null && subflowOutput != null) { - outputMapper.map(subflowOutput, context, new MappingContextImpl(context.getMessageContext())); + public void mapSubflowOutput(AttributeMap output, RequestContext context) { + if (outputMapper != null && output != null) { + MappingResults results = outputMapper.map(output, context); + if (results != null && results.hasErrorResults()) { + throw new FlowOutputMappingException(context.getActiveFlow().getId(), + context.getCurrentState().getId(), results); + } } } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/expression/el/WebFlowELExpressionParser.java b/spring-webflow/src/main/java/org/springframework/webflow/expression/el/WebFlowELExpressionParser.java index 72a9ee80..23f8881b 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/expression/el/WebFlowELExpressionParser.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/expression/el/WebFlowELExpressionParser.java @@ -27,7 +27,6 @@ import javax.el.VariableMapper; import org.springframework.binding.expression.el.DefaultELResolver; import org.springframework.binding.expression.el.ELContextFactory; import org.springframework.binding.expression.el.ELExpressionParser; -import org.springframework.webflow.core.collection.MutableAttributeMap; import org.springframework.webflow.execution.RequestContext; /** @@ -44,7 +43,6 @@ public class WebFlowELExpressionParser extends ELExpressionParser { public WebFlowELExpressionParser(ExpressionFactory expressionFactory) { super(expressionFactory); putContextFactory(RequestContext.class, new RequestContextELContextFactory()); - putContextFactory(MutableAttributeMap.class, new AttributeMapELContextFactory()); } /** @@ -65,17 +63,6 @@ public class WebFlowELExpressionParser extends ELExpressionParser { } } - /** - * Configures EL context instances for evaluating against an AttributeMap. - * @author Keith Donald - */ - private static class AttributeMapELContextFactory implements ELContextFactory { - public ELContext getELContext(Object target) { - ELResolver resolver = new DefaultELResolver(target, null); - return new WebFlowELContext(resolver); - } - } - private static class WebFlowELContext extends ELContext { private ELResolver resolver; diff --git a/spring-webflow/src/test/java/org/springframework/webflow/action/AttributeMapperActionTests.java b/spring-webflow/src/test/java/org/springframework/webflow/action/AttributeMapperActionTests.java deleted file mode 100644 index 87e992de..00000000 --- a/spring-webflow/src/test/java/org/springframework/webflow/action/AttributeMapperActionTests.java +++ /dev/null @@ -1,59 +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.action; - -import junit.framework.TestCase; - -import org.springframework.binding.mapping.DefaultAttributeMapper; -import org.springframework.binding.mapping.MappingBuilder; -import org.springframework.webflow.expression.DefaultExpressionParserFactory; -import org.springframework.webflow.test.MockRequestContext; - -/** - * Unit test for the {@link AttributeMapperAction}. - * - * @author Erwin Vervaet - */ -public class AttributeMapperActionTests extends TestCase { - - private MappingBuilder mappingBuilder = new MappingBuilder(DefaultExpressionParserFactory.getExpressionParser()); - - public void testMapping() throws Exception { - DefaultAttributeMapper mapper = new DefaultAttributeMapper(); - mapper.addMapping(mappingBuilder.source("externalContext.requestParameterMap.foo").target("flowScope.bar") - .value()); - AttributeMapperAction action = new AttributeMapperAction(mapper); - - MockRequestContext context = new MockRequestContext(); - context.putRequestParameter("foo", "value"); - - assertTrue(context.getFlowScope().size() == 0); - - action.execute(context); - - assertEquals(1, context.getFlowScope().size()); - assertEquals("value", context.getFlowScope().get("bar")); - } - - public void testNullIllegalArgument() { - try { - new AttributeMapperAction(null); - fail("Should've thrown illegal argument"); - } catch (IllegalArgumentException e) { - - } - } -} \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/action/BindActionTests.java b/spring-webflow/src/test/java/org/springframework/webflow/action/BindActionTests.java index 18bb8baa..39bda3a8 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/action/BindActionTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/action/BindActionTests.java @@ -7,7 +7,6 @@ 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; @@ -86,11 +85,6 @@ public class BindActionTests extends TestCase { BindBean bean = (BindBean) context.getFlowScope().get("bindTarget"); assertEquals("foo", bean.getStringProperty()); assertEquals(new Integer(3), 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 void testBindWithEmptyAttributes() throws Exception { diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/EndStateTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/EndStateTests.java index fad32479..8338b8f1 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/EndStateTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/EndStateTests.java @@ -18,10 +18,12 @@ package org.springframework.webflow.engine; import junit.framework.TestCase; import org.springframework.binding.expression.EvaluationException; +import org.springframework.binding.expression.Expression; +import org.springframework.binding.expression.ExpressionParser; import org.springframework.binding.expression.support.AbstractGetValueExpression; -import org.springframework.binding.mapping.DefaultAttributeMapper; -import org.springframework.binding.mapping.Mapping; -import org.springframework.binding.mapping.MappingBuilder; +import org.springframework.binding.expression.support.ParserContextImpl; +import org.springframework.binding.mapping.impl.DefaultMapper; +import org.springframework.binding.mapping.impl.DefaultMapping; import org.springframework.webflow.core.collection.MutableAttributeMap; import org.springframework.webflow.engine.support.DefaultTargetStateResolver; import org.springframework.webflow.engine.support.EventIdTransitionCriteria; @@ -65,10 +67,11 @@ public class EndStateTests extends TestCase { } }; EndState state = new EndState(flow, "end"); - DefaultAttributeMapper mapper = new DefaultAttributeMapper(); - MappingBuilder builder = new MappingBuilder(DefaultExpressionParserFactory.getExpressionParser()); - Mapping mapping = builder.source("flowScope.x").target("y").value(); - mapper.addMapping(mapping); + DefaultMapper mapper = new DefaultMapper(); + ExpressionParser parser = DefaultExpressionParserFactory.getExpressionParser(); + Expression x = parser.parseExpression("flowScope.x", new ParserContextImpl().eval(RequestContext.class)); + Expression y = parser.parseExpression("y", new ParserContextImpl().eval(MutableAttributeMap.class)); + mapper.addMapping(new DefaultMapping(x, y)); state.setOutputMapper(mapper); MockRequestControlContext context = new MockRequestControlContext(flow); context.getFlowScope().put("x", "foo"); diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/FlowTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/FlowTests.java index 3044dcdd..96f88d30 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/FlowTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/FlowTests.java @@ -19,11 +19,16 @@ import java.util.ArrayList; import junit.framework.TestCase; -import org.springframework.binding.mapping.DefaultAttributeMapper; -import org.springframework.binding.mapping.MappingBuilder; +import org.springframework.binding.expression.Expression; +import org.springframework.binding.expression.ExpressionParser; +import org.springframework.binding.expression.support.ParserContextImpl; +import org.springframework.binding.mapping.impl.DefaultMapper; +import org.springframework.binding.mapping.impl.DefaultMapping; import org.springframework.webflow.TestException; import org.springframework.webflow.action.TestMultiAction; +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.support.DefaultTargetStateResolver; import org.springframework.webflow.engine.support.EventIdTransitionCriteria; import org.springframework.webflow.engine.support.TransitionExecutingFlowExecutionExceptionHandler; @@ -194,9 +199,11 @@ public class FlowTests extends TestCase { } public void testStartWithMapper() { - DefaultAttributeMapper attributeMapper = new DefaultAttributeMapper(); - MappingBuilder mapping = new MappingBuilder(DefaultExpressionParserFactory.getExpressionParser()); - attributeMapper.addMapping(mapping.source("attr").target("flowScope.attr").value()); + DefaultMapper attributeMapper = new DefaultMapper(); + ExpressionParser parser = DefaultExpressionParserFactory.getExpressionParser(); + Expression x = parser.parseExpression("attr", new ParserContextImpl().eval(AttributeMap.class)); + Expression y = parser.parseExpression("flowScope.attr", new ParserContextImpl().eval(RequestContext.class)); + attributeMapper.addMapping(new DefaultMapping(x, y)); flow.setInputMapper(attributeMapper); MockRequestControlContext context = new MockRequestControlContext(flow); LocalAttributeMap sessionInput = new LocalAttributeMap(); @@ -206,9 +213,11 @@ public class FlowTests extends TestCase { } public void testStartWithMapperButNoInput() { - DefaultAttributeMapper attributeMapper = new DefaultAttributeMapper(); - MappingBuilder mapping = new MappingBuilder(DefaultExpressionParserFactory.getExpressionParser()); - attributeMapper.addMapping(mapping.source("attr").target("flowScope.attr").value()); + DefaultMapper attributeMapper = new DefaultMapper(); + ExpressionParser parser = DefaultExpressionParserFactory.getExpressionParser(); + Expression x = parser.parseExpression("attr", new ParserContextImpl().eval(AttributeMap.class)); + Expression y = parser.parseExpression("flowScope.attr", new ParserContextImpl().eval(RequestContext.class)); + attributeMapper.addMapping(new DefaultMapping(x, y)); flow.setInputMapper(attributeMapper); MockRequestControlContext context = new MockRequestControlContext(flow); LocalAttributeMap sessionInput = new LocalAttributeMap(); @@ -293,9 +302,11 @@ public class FlowTests extends TestCase { } public void testEndWithOutputMapper() { - DefaultAttributeMapper attributeMapper = new DefaultAttributeMapper(); - MappingBuilder mapping = new MappingBuilder(DefaultExpressionParserFactory.getExpressionParser()); - attributeMapper.addMapping(mapping.source("flowScope.attr").target("attr").value()); + DefaultMapper attributeMapper = new DefaultMapper(); + ExpressionParser parser = DefaultExpressionParserFactory.getExpressionParser(); + Expression x = parser.parseExpression("flowScope.attr", new ParserContextImpl().eval(RequestContext.class)); + Expression y = parser.parseExpression("attr", new ParserContextImpl().eval(MutableAttributeMap.class)); + attributeMapper.addMapping(new DefaultMapping(x, y)); flow.setOutputMapper(attributeMapper); MockRequestControlContext context = new MockRequestControlContext(flow); context.getFlowScope().put("attr", "foo"); diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/SubflowStateTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/SubflowStateTests.java index 1341f783..f2bab153 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/SubflowStateTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/SubflowStateTests.java @@ -15,12 +15,15 @@ */ package org.springframework.webflow.engine; +import java.util.Collections; + import junit.framework.TestCase; import org.springframework.binding.expression.EvaluationException; import org.springframework.binding.expression.support.AbstractGetValueExpression; -import org.springframework.binding.mapping.AttributeMapper; -import org.springframework.binding.mapping.MappingContext; +import org.springframework.binding.mapping.Mapper; +import org.springframework.binding.mapping.MappingResults; +import org.springframework.binding.mapping.impl.DefaultMappingResults; import org.springframework.webflow.core.collection.AttributeMap; import org.springframework.webflow.core.collection.LocalAttributeMap; import org.springframework.webflow.core.collection.MutableAttributeMap; @@ -65,17 +68,18 @@ public class SubflowStateTests extends TestCase { public void testEnterWithInput() { subflowState.setAttributeMapper(new SubflowAttributeMapper() { - public MutableAttributeMap createFlowInput(RequestContext context) { + public MutableAttributeMap createSubflowInput(RequestContext context) { return new LocalAttributeMap("foo", "bar"); } - public void mapFlowOutput(AttributeMap flowOutput, RequestContext context) { + public void mapSubflowOutput(AttributeMap flowOutput, RequestContext context) { } }); - subflow.setInputMapper(new AttributeMapper() { - public void map(Object source, Object target, MappingContext context) { + subflow.setInputMapper(new Mapper() { + public MappingResults map(Object source, Object target) { MutableAttributeMap map = (MutableAttributeMap) source; assertEquals("bar", map.get("foo")); + return new DefaultMappingResults(source, target, Collections.EMPTY_LIST); } }); new State(subflow, "whatev") { @@ -88,11 +92,11 @@ public class SubflowStateTests extends TestCase { public void testReturnWithOutput() { subflowState.setAttributeMapper(new SubflowAttributeMapper() { - public MutableAttributeMap createFlowInput(RequestContext context) { + public MutableAttributeMap createSubflowInput(RequestContext context) { return new LocalAttributeMap(); } - public void mapFlowOutput(AttributeMap flowOutput, RequestContext context) { + public void mapSubflowOutput(AttributeMap flowOutput, RequestContext context) { assertEquals("bar", flowOutput.get("foo")); } }); @@ -101,14 +105,13 @@ public class SubflowStateTests extends TestCase { protected void doEnter(RequestControlContext context) throws FlowExecutionException { } }; - new EndState(subflow, "end"); - subflow.setOutputMapper(new AttributeMapper() { - public void map(Object source, Object target, MappingContext context) { + subflow.setOutputMapper(new Mapper() { + public MappingResults map(Object source, Object target) { MutableAttributeMap map = (MutableAttributeMap) target; map.put("foo", "bar"); + return new DefaultMappingResults(source, target, Collections.EMPTY_LIST); } - }); subflowState.enter(context); assertEquals("parent", context.getActiveFlow().getId()); diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/TestSubflowAttributeMapper.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/TestSubflowAttributeMapper.java index 51bca8e0..7abbaf0c 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/TestSubflowAttributeMapper.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/TestSubflowAttributeMapper.java @@ -21,13 +21,13 @@ import org.springframework.webflow.core.collection.MutableAttributeMap; import org.springframework.webflow.execution.RequestContext; class TestSubflowAttributeMapper implements SubflowAttributeMapper { - public MutableAttributeMap createFlowInput(RequestContext context) { + public MutableAttributeMap createSubflowInput(RequestContext context) { LocalAttributeMap inputMap = new LocalAttributeMap(); inputMap.put("childInputAttribute", context.getFlowScope().get("parentInputAttribute")); return inputMap; } - public void mapFlowOutput(AttributeMap subflowOutput, RequestContext context) { + public void mapSubflowOutput(AttributeMap subflowOutput, RequestContext context) { MutableAttributeMap parentAttributes = context.getFlowExecutionContext().getActiveSession().getScope(); parentAttributes.put("parentOutputAttribute", subflowOutput.get("childInputAttribute")); } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilderTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilderTests.java index 90d2a48d..8fb94275 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilderTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilderTests.java @@ -3,13 +3,14 @@ package org.springframework.webflow.engine.builder.xml; import junit.framework.TestCase; import org.springframework.beans.factory.support.StaticListableBeanFactory; -import org.springframework.binding.mapping.AttributeMappingException; import org.springframework.core.io.ClassPathResource; import org.springframework.webflow.action.ExternalRedirectAction; import org.springframework.webflow.action.FlowDefinitionRedirectAction; import org.springframework.webflow.core.collection.LocalAttributeMap; import org.springframework.webflow.core.collection.MutableAttributeMap; import org.springframework.webflow.engine.Flow; +import org.springframework.webflow.engine.FlowInputMappingException; +import org.springframework.webflow.engine.FlowOutputMappingException; import org.springframework.webflow.engine.ViewState; import org.springframework.webflow.engine.builder.FlowAssembler; import org.springframework.webflow.engine.builder.FlowBuilderException; @@ -17,7 +18,6 @@ import org.springframework.webflow.engine.builder.support.ActionExecutingViewFac import org.springframework.webflow.engine.impl.FlowExecutionImplFactory; import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.FlowExecution; -import org.springframework.webflow.execution.FlowExecutionException; import org.springframework.webflow.execution.ViewFactory; import org.springframework.webflow.security.SecurityRule; import org.springframework.webflow.test.MockExternalContext; @@ -122,8 +122,7 @@ public class XmlFlowBuilderTests extends TestCase { try { execution.start(input, context); fail("Should have failed"); - } catch (FlowExecutionException e) { - AttributeMappingException me = (AttributeMappingException) e.getCause(); + } catch (FlowInputMappingException e) { } } @@ -140,8 +139,7 @@ public class XmlFlowBuilderTests extends TestCase { try { execution.start(input, context); fail("Should have failed"); - } catch (FlowExecutionException e) { - AttributeMappingException me = (AttributeMappingException) e.getCause(); + } catch (FlowOutputMappingException e) { } } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/test/SearchFlowExecutionTests.java b/spring-webflow/src/test/java/org/springframework/webflow/test/SearchFlowExecutionTests.java index 80a153eb..77762cf1 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/test/SearchFlowExecutionTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/test/SearchFlowExecutionTests.java @@ -18,8 +18,8 @@ package org.springframework.webflow.test; import java.util.ArrayList; import java.util.List; -import org.springframework.binding.mapping.AttributeMapper; -import org.springframework.binding.mapping.MappingContext; +import org.springframework.binding.mapping.Mapper; +import org.springframework.binding.mapping.MappingResults; import org.springframework.webflow.config.FlowDefinitionResource; import org.springframework.webflow.config.FlowDefinitionResourceFactory; import org.springframework.webflow.context.ExternalContext; @@ -87,10 +87,11 @@ public class SearchFlowExecutionTests extends AbstractXmlFlowExecutionTests { protected void configureFlowBuilderContext(MockFlowBuilderContext builderContext) { Flow mockDetailFlow = new Flow("detail-flow"); - mockDetailFlow.setInputMapper(new AttributeMapper() { - public void map(Object source, Object target, MappingContext context) { + mockDetailFlow.setInputMapper(new Mapper() { + public MappingResults map(Object source, Object target) { assertEquals("id of value 1 not provided as input by calling search flow", new Long(1), ((AttributeMap) source).get("id")); + return null; } }); // test responding to finish result