SWF-466 - Add a "type" attribute to "evaluation-result" to support type conversion of the action result.
This commit is contained in:
@@ -91,7 +91,7 @@ public class MethodInvoker {
|
||||
+ StylerUtils.style(arguments) + " on bean [" + bean + "]");
|
||||
|
||||
}
|
||||
Object returnValue = applyTypeConversion(method.invoke(bean, arguments), signature.getDesiredResultType());
|
||||
Object returnValue = method.invoke(bean, arguments);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Invoked method with signature [" + key + "] returned value [" + returnValue + "]");
|
||||
}
|
||||
|
||||
@@ -38,17 +38,12 @@ public class MethodSignature {
|
||||
*/
|
||||
private Parameters parameters;
|
||||
|
||||
/**
|
||||
* The desired result type of the method invocation
|
||||
*/
|
||||
private Class desiredResultType;
|
||||
|
||||
/**
|
||||
* Creates a method signature with no parameters.
|
||||
* @param methodName the name of the method
|
||||
*/
|
||||
public MethodSignature(String methodName) {
|
||||
this(methodName, Parameters.NONE, null);
|
||||
this(methodName, Parameters.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,21 +52,19 @@ public class MethodSignature {
|
||||
* @param parameter the method parameter
|
||||
*/
|
||||
public MethodSignature(String methodName, Parameter parameter) {
|
||||
this(methodName, new Parameters(parameter), null);
|
||||
this(methodName, new Parameters(parameter));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a method signature with a list of parameters.
|
||||
* @param methodName the name of the method
|
||||
* @param parameters the method parameters
|
||||
* @param desiredResultType the desired result type of the method invocation
|
||||
*/
|
||||
public MethodSignature(String methodName, Parameters parameters, Class desiredResultType) {
|
||||
public MethodSignature(String methodName, Parameters parameters) {
|
||||
Assert.notNull(methodName, "The method name is required");
|
||||
Assert.notNull(parameters, "The parameters are required");
|
||||
this.methodName = methodName;
|
||||
this.parameters = parameters;
|
||||
this.desiredResultType = desiredResultType;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,14 +81,6 @@ public class MethodSignature {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the desired result type of the method invocation
|
||||
* @return the desired result type
|
||||
*/
|
||||
public Class getDesiredResultType() {
|
||||
return desiredResultType;
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof MethodSignature)) {
|
||||
return false;
|
||||
|
||||
@@ -56,23 +56,17 @@ public class MethodInvokerTests extends TestCase {
|
||||
Parameters parameters = new Parameters();
|
||||
Bean bean = new Bean();
|
||||
parameters.add(new Parameter(Bean.class, new StaticExpression(bean)));
|
||||
MethodSignature method = new MethodSignature("testBeanArg", parameters, null);
|
||||
MethodSignature method = new MethodSignature("testBeanArg", parameters);
|
||||
assertSame(bean, methodInvoker.invoke(method, new TestObject(), null));
|
||||
}
|
||||
|
||||
public void testPrimitiveArg() {
|
||||
Parameters parameters = new Parameters();
|
||||
parameters.add(new Parameter(Boolean.class, new StaticExpression(Boolean.TRUE)));
|
||||
MethodSignature method = new MethodSignature("testPrimitiveArg", parameters, null);
|
||||
MethodSignature method = new MethodSignature("testPrimitiveArg", parameters);
|
||||
assertEquals(Boolean.TRUE, methodInvoker.invoke(method, new TestObject(), null));
|
||||
}
|
||||
|
||||
public void testResultConversion() {
|
||||
Parameters parameters = Parameters.NONE;
|
||||
MethodSignature method = new MethodSignature("testConvertResult", parameters, Class.class);
|
||||
assertEquals(Object.class, methodInvoker.invoke(method, new TestObject(), null));
|
||||
}
|
||||
|
||||
private static class TestObject {
|
||||
|
||||
public void test() {
|
||||
@@ -83,10 +77,6 @@ public class MethodInvokerTests extends TestCase {
|
||||
return bean;
|
||||
}
|
||||
|
||||
public String testConvertResult() {
|
||||
return "java.lang.Object";
|
||||
}
|
||||
|
||||
public boolean testPrimitiveArg(boolean primitive) {
|
||||
return primitive;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ package org.springframework.webflow.action;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.binding.convert.ConversionService;
|
||||
import org.springframework.binding.convert.support.DefaultConversionService;
|
||||
import org.springframework.binding.expression.Expression;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -45,15 +47,27 @@ public class ActionResultExposer implements Serializable {
|
||||
*/
|
||||
private ScopeType resultScope;
|
||||
|
||||
/**
|
||||
* The desired type to expose the result as
|
||||
*/
|
||||
private Class desiredResultType;
|
||||
|
||||
/**
|
||||
* The {@link ConversionService} to use to convert the result to the desired type
|
||||
*/
|
||||
private ConversionService conversionService = new DefaultConversionService();
|
||||
|
||||
/**
|
||||
* Creates a action result exposer
|
||||
* @param nameExpression the result name
|
||||
* @param resultScope the result scope
|
||||
* @param desiredResultType the desired result type
|
||||
*/
|
||||
public ActionResultExposer(Expression nameExpression, ScopeType resultScope) {
|
||||
public ActionResultExposer(Expression nameExpression, ScopeType resultScope, Class desiredResultType) {
|
||||
Assert.notNull(nameExpression, "The result name is required");
|
||||
this.nameExpression = nameExpression;
|
||||
this.resultScope = resultScope;
|
||||
this.desiredResultType = desiredResultType;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,6 +84,13 @@ public class ActionResultExposer implements Serializable {
|
||||
return resultScope;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the desired result type to be exposed
|
||||
*/
|
||||
public Class getDesiredResultType() {
|
||||
return desiredResultType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose given bean method return value in given flow execution request context.
|
||||
* @param result the return value
|
||||
@@ -78,9 +99,9 @@ public class ActionResultExposer implements Serializable {
|
||||
public void exposeResult(Object result, RequestContext context) {
|
||||
if (resultScope != null) {
|
||||
MutableAttributeMap scopeMap = resultScope.getScope(context);
|
||||
nameExpression.setValue(scopeMap, result);
|
||||
nameExpression.setValue(scopeMap, applyTypeConversion(result, desiredResultType));
|
||||
} else {
|
||||
nameExpression.setValue(context, result);
|
||||
nameExpression.setValue(context, applyTypeConversion(result, desiredResultType));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,4 +109,18 @@ public class ActionResultExposer implements Serializable {
|
||||
return new ToStringCreator(this).append("resultName", nameExpression).append("resultScope", resultScope)
|
||||
.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply type conversion on the supplied value
|
||||
*
|
||||
* @param value the raw value to be converted
|
||||
* @param targetType the target type for the conversion
|
||||
* @return the converted result
|
||||
*/
|
||||
protected Object applyTypeConversion(Object value, Class targetType) {
|
||||
if (value == null || targetType == null) {
|
||||
return value;
|
||||
}
|
||||
return conversionService.getConversionExecutor(value.getClass(), targetType).execute(value);
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import org.springframework.webflow.execution.RequestContext;
|
||||
* @see ResultEventFactorySelector
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Jeremy Grelle
|
||||
*/
|
||||
public class EvaluateAction extends AbstractAction {
|
||||
|
||||
|
||||
@@ -754,8 +754,7 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde
|
||||
String beanId = element.getAttribute(BEAN_ATTRIBUTE);
|
||||
String methodName = element.getAttribute(METHOD_ATTRIBUTE);
|
||||
Parameters parameters = parseMethodParameters(element);
|
||||
Class desiredResultType = parseMethodResultType(element);
|
||||
MethodSignature methodSignature = new MethodSignature(methodName, parameters, desiredResultType);
|
||||
MethodSignature methodSignature = new MethodSignature(methodName, parameters);
|
||||
ActionResultExposer resultExposer = parseMethodResultExposer(element);
|
||||
return getLocalContext().getBeanInvokingActionFactory().createBeanInvokingAction(beanId,
|
||||
getLocalContext().getBeanFactory(), methodSignature, resultExposer,
|
||||
@@ -784,8 +783,7 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private Class parseMethodResultType(Element element) {
|
||||
Element resultElement = DomUtils.getChildElementByTagName(element, METHOD_RESULT_ELEMENT);
|
||||
private Class parseResultType(Element resultElement) {
|
||||
Class type = null;
|
||||
if (resultElement != null) {
|
||||
if (resultElement.hasAttribute(TYPE_ATTRIBUTE)) {
|
||||
@@ -815,7 +813,7 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde
|
||||
nameExpression = getExpressionParser().parseExpression(nameExpressionString,
|
||||
new ParserContextImpl().eval(RequestContext.class));
|
||||
}
|
||||
return new ActionResultExposer(nameExpression, scope);
|
||||
return new ActionResultExposer(nameExpression, scope, parseResultType(element));
|
||||
}
|
||||
|
||||
private AnnotatedAction parseAnnotatedEvaluateAction(Element element) {
|
||||
|
||||
@@ -1139,6 +1139,16 @@ If not specified the name attribute must be a fully resolvable expression such a
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="type" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
<![CDATA[
|
||||
The desired evaluation result type. Optional. If specified and the method return value is not of the
|
||||
desired result type, a type conversion will be attempted.
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
@@ -23,7 +23,7 @@ public class ActionResultExposerTests extends TestCase {
|
||||
Expression nameExpression = parser.parseExpression("#{foo}", new ParserContextImpl()
|
||||
.eval(MutableAttributeMap.class));
|
||||
|
||||
ActionResultExposer exposer = new ActionResultExposer(nameExpression, ScopeType.REQUEST);
|
||||
ActionResultExposer exposer = new ActionResultExposer(nameExpression, ScopeType.REQUEST, null);
|
||||
|
||||
RequestContext context = new MockRequestContext();
|
||||
|
||||
@@ -33,6 +33,24 @@ public class ActionResultExposerTests extends TestCase {
|
||||
assertEquals("Value stored at key 'foo' is incorrect", valueToSet, context.getRequestScope().get("foo"));
|
||||
}
|
||||
|
||||
public void testExposeResult_ScopeSpecifiedWithTypeConversion() {
|
||||
|
||||
String valueToSet = "true";
|
||||
|
||||
ExpressionParser parser = new WebFlowELExpressionParser(new ExpressionFactoryImpl());
|
||||
Expression nameExpression = parser.parseExpression("#{foo}", new ParserContextImpl()
|
||||
.eval(MutableAttributeMap.class));
|
||||
|
||||
ActionResultExposer exposer = new ActionResultExposer(nameExpression, ScopeType.REQUEST, Boolean.class);
|
||||
|
||||
RequestContext context = new MockRequestContext();
|
||||
|
||||
exposer.exposeResult(valueToSet, context);
|
||||
|
||||
assertTrue("Key 'foo' not found in request scope", context.getRequestScope().contains("foo"));
|
||||
assertEquals("Value stored at key 'foo' is incorrect", Boolean.TRUE, context.getRequestScope().get("foo"));
|
||||
}
|
||||
|
||||
public void testExposeResult_ScopeExpression() {
|
||||
|
||||
String valueToSet = "myValue";
|
||||
@@ -41,7 +59,7 @@ public class ActionResultExposerTests extends TestCase {
|
||||
Expression nameExpression = parser.parseExpression("#{requestScope.foo}", new ParserContextImpl()
|
||||
.eval(RequestContext.class));
|
||||
|
||||
ActionResultExposer exposer = new ActionResultExposer(nameExpression, null);
|
||||
ActionResultExposer exposer = new ActionResultExposer(nameExpression, null, null);
|
||||
|
||||
RequestContext context = new MockRequestContext();
|
||||
|
||||
@@ -51,6 +69,24 @@ public class ActionResultExposerTests extends TestCase {
|
||||
assertEquals("Value stored at key 'foo' is incorrect", valueToSet, context.getRequestScope().get("foo"));
|
||||
}
|
||||
|
||||
public void testExposeResult_ScopeExpressionWithTypeConversion() {
|
||||
|
||||
String valueToSet = "true";
|
||||
|
||||
ExpressionParser parser = new WebFlowELExpressionParser(new ExpressionFactoryImpl());
|
||||
Expression nameExpression = parser.parseExpression("#{requestScope.foo}", new ParserContextImpl()
|
||||
.eval(RequestContext.class));
|
||||
|
||||
ActionResultExposer exposer = new ActionResultExposer(nameExpression, null, Boolean.class);
|
||||
|
||||
RequestContext context = new MockRequestContext();
|
||||
|
||||
exposer.exposeResult(valueToSet, context);
|
||||
|
||||
assertTrue("Key 'foo' not found in request scope", context.getRequestScope().contains("foo"));
|
||||
assertEquals("Value stored at key 'foo' is incorrect", Boolean.TRUE, context.getRequestScope().get("foo"));
|
||||
}
|
||||
|
||||
public void testExposeResult_SearchExpression() {
|
||||
|
||||
String valueToSet = "myValue";
|
||||
@@ -59,7 +95,7 @@ public class ActionResultExposerTests extends TestCase {
|
||||
Expression nameExpression = parser.parseExpression("#{bean.foo}", new ParserContextImpl()
|
||||
.eval(RequestContext.class));
|
||||
|
||||
ActionResultExposer exposer = new ActionResultExposer(nameExpression, null);
|
||||
ActionResultExposer exposer = new ActionResultExposer(nameExpression, null, null);
|
||||
|
||||
RequestContext context = new MockRequestContext();
|
||||
TestBean bean = new TestBean();
|
||||
@@ -78,7 +114,7 @@ public class ActionResultExposerTests extends TestCase {
|
||||
Expression nameExpression = parser.parseExpression("${foo}", new ParserContextImpl()
|
||||
.eval(MutableAttributeMap.class));
|
||||
|
||||
ActionResultExposer exposer = new ActionResultExposer(nameExpression, ScopeType.REQUEST);
|
||||
ActionResultExposer exposer = new ActionResultExposer(nameExpression, ScopeType.REQUEST, null);
|
||||
|
||||
RequestContext context = new MockRequestContext();
|
||||
|
||||
@@ -96,7 +132,7 @@ public class ActionResultExposerTests extends TestCase {
|
||||
Expression nameExpression = parser.parseExpression("${requestScope.foo}", new ParserContextImpl()
|
||||
.eval(RequestContext.class));
|
||||
|
||||
ActionResultExposer exposer = new ActionResultExposer(nameExpression, null);
|
||||
ActionResultExposer exposer = new ActionResultExposer(nameExpression, null, null);
|
||||
|
||||
RequestContext context = new MockRequestContext();
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ import org.springframework.webflow.test.MockRequestContext;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link EvaluateAction}.
|
||||
*
|
||||
* @author Jeremy Grelle
|
||||
*/
|
||||
public class EvaluateActionTests extends TestCase {
|
||||
|
||||
@@ -54,7 +56,7 @@ public class EvaluateActionTests extends TestCase {
|
||||
.eval(MutableAttributeMap.class));
|
||||
|
||||
EvaluateAction action = new EvaluateAction(new StaticExpression("bar"), new ActionResultExposer(nameExpression,
|
||||
ScopeType.FLOW));
|
||||
ScopeType.FLOW, null));
|
||||
Event result = action.execute(context);
|
||||
assertEquals("bar", result.getId());
|
||||
assertEquals("bar", context.getFlowScope().get("baz"));
|
||||
@@ -68,19 +70,31 @@ public class EvaluateActionTests extends TestCase {
|
||||
TestBean bean = new TestBean();
|
||||
|
||||
EvaluateAction action = new EvaluateAction(new StaticExpression(bean), new ActionResultExposer(nameExpression,
|
||||
ScopeType.FLOW));
|
||||
ScopeType.FLOW, null));
|
||||
Event result = action.execute(context);
|
||||
assertEquals("success", result.getId());
|
||||
assertEquals(bean, context.getFlowScope().get("baz"));
|
||||
}
|
||||
|
||||
public void testStringResult_ScopeSpecifiedWithTypeConversion() throws Exception {
|
||||
ExpressionParser parser = new WebFlowELExpressionParser(new ExpressionFactoryImpl());
|
||||
Expression nameExpression = parser.parseExpression("#{baz}", new ParserContextImpl()
|
||||
.eval(MutableAttributeMap.class));
|
||||
|
||||
EvaluateAction action = new EvaluateAction(new StaticExpression("true"), new ActionResultExposer(
|
||||
nameExpression, ScopeType.FLOW, Boolean.class));
|
||||
Event result = action.execute(context);
|
||||
assertEquals("true", result.getId());
|
||||
assertEquals(Boolean.TRUE, context.getFlowScope().get("baz"));
|
||||
}
|
||||
|
||||
public void testEvaluateExpressionResult_ScopeExpression() throws Exception {
|
||||
ExpressionParser parser = new WebFlowELExpressionParser(new ExpressionFactoryImpl());
|
||||
Expression nameExpression = parser.parseExpression("#{flowScope.baz}", new ParserContextImpl()
|
||||
.eval(MutableAttributeMap.class));
|
||||
|
||||
EvaluateAction action = new EvaluateAction(new StaticExpression("bar"), new ActionResultExposer(nameExpression,
|
||||
null));
|
||||
null, null));
|
||||
Event result = action.execute(context);
|
||||
assertEquals("bar", result.getId());
|
||||
assertEquals("bar", context.getFlowScope().get("baz"));
|
||||
@@ -95,7 +109,7 @@ public class EvaluateActionTests extends TestCase {
|
||||
context.getFlowScope().put("baz", bean);
|
||||
|
||||
EvaluateAction action = new EvaluateAction(new StaticExpression("bar"), new ActionResultExposer(nameExpression,
|
||||
null));
|
||||
null, null));
|
||||
Event result = action.execute(context);
|
||||
assertEquals("bar", result.getId());
|
||||
assertEquals("bar", bean.getFoo());
|
||||
|
||||
Reference in New Issue
Block a user