enhanced mapper subsystem

This commit is contained in:
Keith Donald
2008-03-16 20:53:19 +00:00
parent 3cc4c93eba
commit 4cbb5ac1cf
43 changed files with 968 additions and 631 deletions

View File

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

View File

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

View File

@@ -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.
* <p>
* 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;
}

View File

@@ -1,5 +0,0 @@
package org.springframework.binding.mapping;
public class AttributeMappingException extends RuntimeException {
}

View File

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

View File

@@ -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.
* <p>
* Example usage:
*
* <pre>
* MappingBuilder mapping = new MappingBuilder();
* Mapping result = mapping.source(&quot;foo&quot;).target(&quot;bar&quot;).from(String.class).to(Long.class).value();
* </pre>
*
* 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;
}
}

View File

@@ -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:
* <ol>
* <li>Exposing information to a mapper to influence a mapping attempt.
* <li>Providing operations for recording progress or errors during the mapping process.
* </ol>
* 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();
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
package org.springframework.binding.mapping;
public interface MappingResultsCriteria {
public boolean test(MappingResult result);
}

View File

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

View File

@@ -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() {

View File

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

View File

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

View File

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

View File

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

View File

@@ -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";
}
}

View File

@@ -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";
}
}

View File

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

View File

@@ -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";
}
}
}

View File

@@ -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";
}
}

View File

@@ -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}").
* <p>
* 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());
}
}

View File

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

View File

@@ -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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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) {

View File

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

View File

@@ -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;

View File

@@ -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) {
}
}
}

View File

@@ -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 {

View File

@@ -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");

View File

@@ -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");

View File

@@ -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());

View File

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

View File

@@ -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) {
}
}

View File

@@ -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