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:
- *
- * - Exposing information to a mapper to influence a mapping attempt.
- *
- 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 {
*
* - Create (setup) all registered flow variables ({@link #addVariable(FlowVariable)}) in flow scope.
* - Map provided input data into the flow. Typically data will be mapped into flow scope using the registered
- * input mapper ({@link #setInputMapper(AttributeMapper)}).
+ * input mapper ({@link #setInputMapper(Mapper)}).
* - Execute all registered start actions ({@link #getStartActionList()}).
* - Enter the configured start state ({@link #setStartState(State)})
*
@@ -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 {
*
* - Execute all registered end actions ({@link #getEndActionList()}).
* - Map data available in the flow execution control context into provided output map using a registered output
- * mapper ({@link #setOutputMapper(AttributeMapper)}).
+ * 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