From 0a45b2c15d3900e5319f8c1d34d49dc1fd899faf Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Mon, 3 Mar 2008 23:33:58 +0000 Subject: [PATCH] replaced action/bean-action/evaluate-action with evaluate/render/set introduced render renamed set.attribute to set.name renamed evaluate.type to evaluate.result-type made subflow.state subflow dynamic w/ late binding added end-state.commit meta attribute --- spring-webflow/.classpath | 13 +- .../webflow/action/AbstractAction.java | 3 - .../webflow/action/ActionResultExposer.java | 74 +- .../webflow/action/EvaluateAction.java | 20 +- .../webflow/action/FormAction.java | 2 +- .../webflow/action/RenderAction.java | 58 ++ .../webflow/action/SetAction.java | 27 +- .../webflow/engine/FlowAttributeMapper.java | 102 --- .../engine/SubflowAttributeMapper.java | 26 + .../webflow/engine/SubflowState.java | 62 +- .../webflow/engine/ViewState.java | 9 +- .../engine/builder/FlowArtifactFactory.java | 7 +- .../engine/builder/xml/XmlFlowBuilder.java | 661 ++++++---------- .../engine/builder/xml/spring-webflow-2.0.xsd | 719 +++--------------- ...va => AbstractSubflowAttributeMapper.java} | 27 +- .../GenericSubflowAttributeMapper.java} | 12 +- .../support/TransitionCriteriaChain.java | 4 +- .../webflow/execution/View.java | 5 + spring-webflow/src/test/java/log4j.xml | 44 ++ .../action/ActionResultExposerTests.java | 167 +--- .../webflow/action/EvaluateActionTests.java | 106 +-- .../webflow/action/RenderActionTests.java | 43 ++ .../webflow/action/SetActionTests.java | 183 +---- .../webflow/engine/EndStateTests.java | 10 +- .../webflow/engine/SubflowStateTests.java | 12 +- ...r.java => TestSubflowAttributeMapper.java} | 2 +- .../xml/flow-action-evaluate-action.xml | 14 + .../builder/xml/flow-action-evaluate-bean.xml | 11 + .../webflow/test/search-flow.xml | 25 +- 29 files changed, 681 insertions(+), 1767 deletions(-) create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/action/RenderAction.java delete mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/FlowAttributeMapper.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/SubflowAttributeMapper.java rename spring-webflow/src/main/java/org/springframework/webflow/engine/support/{AbstractFlowAttributeMapper.java => AbstractSubflowAttributeMapper.java} (69%) rename spring-webflow/src/main/java/org/springframework/webflow/engine/{builder/xml/ImmutableFlowAttributeMapper.java => support/GenericSubflowAttributeMapper.java} (73%) create mode 100644 spring-webflow/src/test/java/log4j.xml create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/action/RenderActionTests.java rename spring-webflow/src/test/java/org/springframework/webflow/engine/{TestAttributeMapper.java => TestSubflowAttributeMapper.java} (95%) create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-action-evaluate-action.xml create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-action-evaluate-bean.xml diff --git a/spring-webflow/.classpath b/spring-webflow/.classpath index bb2c7e52..78e74c08 100644 --- a/spring-webflow/.classpath +++ b/spring-webflow/.classpath @@ -10,7 +10,9 @@ + + @@ -21,12 +23,16 @@ + + + + @@ -35,6 +41,7 @@ + @@ -42,11 +49,5 @@ - - - - - - diff --git a/spring-webflow/src/main/java/org/springframework/webflow/action/AbstractAction.java b/spring-webflow/src/main/java/org/springframework/webflow/action/AbstractAction.java index 0dcc25ce..7b140816 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/action/AbstractAction.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/action/AbstractAction.java @@ -183,9 +183,6 @@ public abstract class AbstractAction implements Action, InitializingBean { } public final Event execute(RequestContext context) throws Exception { - if (logger.isDebugEnabled()) { - logger.debug("Action '" + getActionNameForLogging() + "' beginning execution"); - } Event result = doPreExecute(context); if (result == null) { result = doExecute(context); diff --git a/spring-webflow/src/main/java/org/springframework/webflow/action/ActionResultExposer.java b/spring-webflow/src/main/java/org/springframework/webflow/action/ActionResultExposer.java index 99641050..8c8eeade 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/action/ActionResultExposer.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/action/ActionResultExposer.java @@ -18,13 +18,10 @@ 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; -import org.springframework.webflow.core.collection.MutableAttributeMap; import org.springframework.webflow.execution.RequestContext; -import org.springframework.webflow.execution.ScopeType; /** * Specifies how an action result value should be exposed to an executing flow. The return value is exposed as an @@ -38,57 +35,48 @@ import org.springframework.webflow.execution.ScopeType; public class ActionResultExposer implements Serializable { /** - * The name of the attribute to index the return value with. + * The expression to set the result to. */ - private Expression nameExpression; - - /** - * The scope of the attribute indexing the return value. - */ - private ScopeType resultScope; + private Expression resultExpression; /** * The desired type to expose the result as */ - private Class desiredResultType; + private Class expectedResultType; /** * The {@link ConversionService} to use to convert the result to the desired type */ - private ConversionService conversionService = new DefaultConversionService(); + private ConversionService conversionService; /** * Creates a action result exposer - * @param nameExpression the result name - * @param resultScope the result scope - * @param desiredResultType the desired result type + * @param resultExpression the result expression + * @param expectedResultType the expected result type */ - 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; + public ActionResultExposer(Expression resultExpression, Class expectedResultType, + ConversionService conversionService) { + Assert.notNull(resultExpression, "The result expression is required"); + this.resultExpression = resultExpression; + this.expectedResultType = expectedResultType; + if (this.expectedResultType != null) { + Assert.notNull(conversionService, "A conversionService is required with an expectedResultType"); + this.conversionService = conversionService; + } } /** * Returns name of the attribute to index the return value with. */ public Expression getNameExpression() { - return nameExpression; - } - - /** - * Returns the scope the attribute indexing the return value. - */ - public ScopeType getResultScope() { - return resultScope; + return resultExpression; } /** * Returns the desired result type to be exposed */ - public Class getDesiredResultType() { - return desiredResultType; + public Class getExpectedResultType() { + return expectedResultType; } /** @@ -97,34 +85,22 @@ public class ActionResultExposer implements Serializable { * @param context the request context */ public void exposeResult(Object result, RequestContext context) { - if (resultScope != null) { - MutableAttributeMap scopeMap = resultScope.getScope(context); - nameExpression.setValue(scopeMap, applyTypeConversion(result, desiredResultType)); - } else { - nameExpression.setValue(context, applyTypeConversion(result, desiredResultType)); - } - } - - public String toString() { - return new ToStringCreator(this).append("resultName", nameExpression).append("resultScope", resultScope) - .toString(); + resultExpression.setValue(context, applyTypeConversion(result)); } /** * 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) { + private Object applyTypeConversion(Object value) { + if (expectedResultType == null) { return value; } - return conversionService.getConversionExecutor(value.getClass(), targetType).execute(value); + return conversionService.getConversionExecutor(value.getClass(), expectedResultType).execute(value); } - public void setConversionService(ConversionService conversionService) { - this.conversionService = conversionService; + public String toString() { + return new ToStringCreator(this).append("resultExpression", resultExpression).append("expectedResultType", + expectedResultType).toString(); } } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/action/EvaluateAction.java b/spring-webflow/src/main/java/org/springframework/webflow/action/EvaluateAction.java index 4175a6eb..d60a04a3 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/action/EvaluateAction.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/action/EvaluateAction.java @@ -52,16 +52,8 @@ public class EvaluateAction extends AbstractAction { /** * Create a new evaluate action. - * @param expression the expression to evaluate - */ - public EvaluateAction(Expression expression) { - this(expression, null); - } - - /** - * Create a new evaluate action. - * @param expression the expression to evaluate - * @param evaluationResultExposer the strategy for how the expression result will be exposed to the flow + * @param expression the expression to evaluate (required) + * @param evaluationResultExposer the strategy for how the expression result will be exposed to the flow (optional) */ public EvaluateAction(Expression expression, ActionResultExposer evaluationResultExposer) { Assert.notNull(expression, "The expression this action should evaluate is required"); @@ -69,6 +61,14 @@ public class EvaluateAction extends AbstractAction { this.evaluationResultExposer = evaluationResultExposer; } + /** + * Sets a custom result event factory selector + * @param factorySelector the factor for creating the evaluation action result event + */ + public void setResultEventFactorySelector(ResultEventFactorySelector factorySelector) { + this.resultEventFactorySelector = factorySelector; + } + protected Event doExecute(RequestContext context) throws Exception { Object result = expression.getValue(context); if (evaluationResultExposer != null) { diff --git a/spring-webflow/src/main/java/org/springframework/webflow/action/FormAction.java b/spring-webflow/src/main/java/org/springframework/webflow/action/FormAction.java index 222dfa31..645d7373 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/action/FormAction.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/action/FormAction.java @@ -480,7 +480,7 @@ public class FormAction extends MultiAction implements InitializingBean { * @param context the action execution context, for accessing and setting data in "flow scope" or "request scope" * @return "success" when binding and validation is successful, "error" if there were binding and/or validation * errors - * @throws Exception an unrecoverable exception occured, either checked or unchecked + * @throws Exception an unrecoverable exception occurred, either checked or unchecked */ public Event bindAndValidate(RequestContext context) throws Exception { if (logger.isDebugEnabled()) { diff --git a/spring-webflow/src/main/java/org/springframework/webflow/action/RenderAction.java b/spring-webflow/src/main/java/org/springframework/webflow/action/RenderAction.java new file mode 100644 index 00000000..025135a8 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/action/RenderAction.java @@ -0,0 +1,58 @@ +/* + * 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.expression.Expression; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.RequestContext; +import org.springframework.webflow.execution.View; + +/** + * An action that sets a special attribute that views use to render partial views called "fragments", instead of the + * entire view. + * + * @author Keith Donald + */ +public class RenderAction extends AbstractAction { + + /** + * The expression for setting the scoped attribute value. + */ + private Expression[] fragmentExpressions; + + /** + * Creates a new render action. + * @param fragmentExpressions the set of expressions to resolve the view fragments to render + */ + public RenderAction(Expression[] fragmentExpressions) { + if (fragmentExpressions == null || fragmentExpressions.length == 0) { + throw new IllegalArgumentException( + "You must provide at least one fragment expression to this render action"); + } + this.fragmentExpressions = fragmentExpressions; + } + + protected Event doExecute(RequestContext context) throws Exception { + String[] fragments = new String[fragmentExpressions.length]; + for (int i = 0; i < fragmentExpressions.length; i++) { + Expression exp = fragmentExpressions[i]; + fragments[i] = (String) exp.getValue(context); + } + context.getFlashScope().put(View.RENDER_FRAGMENTS_ATTRIBUTE, fragments); + return success(); + } + +} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/action/SetAction.java b/spring-webflow/src/main/java/org/springframework/webflow/action/SetAction.java index ad50a366..865768cf 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/action/SetAction.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/action/SetAction.java @@ -17,7 +17,6 @@ package org.springframework.webflow.action; import org.springframework.binding.expression.Expression; import org.springframework.util.Assert; -import org.springframework.webflow.core.collection.MutableAttributeMap; import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.RequestContext; import org.springframework.webflow.execution.ScopeType; @@ -32,12 +31,7 @@ public class SetAction extends AbstractAction { /** * The expression for setting the scoped attribute value. */ - private Expression attributeExpression; - - /** - * The target scope. - */ - private ScopeType scope; + private Expression nameExpression; /** * The expression for resolving the scoped attribute value. @@ -46,26 +40,19 @@ public class SetAction extends AbstractAction { /** * Creates a new set attribute action. - * @param attributeExpression the writeable attribute expression - * @param scope the target scope of the attribute - * @param valueExpression the evaluatable attribute value expression + * @param nameExpression the name of the property to set + * @param valueExpression the expression to obtain the new property value */ - public SetAction(Expression attributeExpression, ScopeType scope, Expression valueExpression) { - Assert.notNull(attributeExpression, "The attribute expression is required"); + public SetAction(Expression nameExpression, Expression valueExpression) { + Assert.notNull(nameExpression, "The name expression is required"); Assert.notNull(valueExpression, "The value expression is required"); - this.attributeExpression = attributeExpression; - this.scope = scope; + this.nameExpression = nameExpression; this.valueExpression = valueExpression; } protected Event doExecute(RequestContext context) throws Exception { Object value = valueExpression.getValue(context); - if (scope != null) { - MutableAttributeMap scopeMap = scope.getScope(context); - attributeExpression.setValue(scopeMap, value); - } else { - attributeExpression.setValue(context, value); - } + nameExpression.setValue(context, value); return success(); } } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/FlowAttributeMapper.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/FlowAttributeMapper.java deleted file mode 100644 index c1f2ca52..00000000 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/FlowAttributeMapper.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2004-2007 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.webflow.engine; - -import org.springframework.webflow.core.collection.AttributeMap; -import org.springframework.webflow.core.collection.MutableAttributeMap; -import org.springframework.webflow.execution.RequestContext; - -/** - * A service interface that maps attributes between two flows. Used by the subflow state to map attributes between a - * parent flow and its sub flow. - *

- * An attribute mapper may map attributes of a parent flow down to a child flow as input when the child is - * spawned as a subflow. In addition, a mapper may map output attributes of a subflow into a resuming parent flow as - * output when the child session ends and control is returned to the parent flow. - *

- * For example, say you have the following parent flow session: - *

- * - *

- *     Parent Flow Session
- *     -------------------
- *     -> flow = myFlow
- *     -> flowScope = [map-> attribute1=value1, attribute2=value2, attribute3=value3]
- * 
- * - *

- * For the "Parent Flow Session" above, there are 3 attributes in flow scope ("attribute1", "attribute2" and - * "attribute3", respectively). Any of these three attributes may be mapped as input down to child subflows when those - * subflows are spawned. An implementation of this interface performs the actual mapping, encapsulating knowledge of - * which attributes should be mapped, and how they will be mapped (for example, will the same attribute - * names be used between flows or not?). - *

- * For example: - *

- * - *

- *     Flow Attribute Mapper Configuration
- *     -----------------------------------
- *     -> inputMappings  = [map-> flowScope.attribute1->attribute1, flowScope.attribute3->attribute4]
- *     -> outputMappings = [map-> attribute4->flowScope.attribute3]
- * 
- * - *

- * The above example "Flow Attribute Mapper" specifies inputMappings that define which parent attributes - * to map as input to the child. In this case, two attributes in flow scope of the parent are mapped, "attribute1" and - * "attribute3". "attribute1" is mapped with the name "attribute1" (given the same name in both flows), while - * "attribute3" is mapped to "attribute4", given a different name that is local to the child flow. - *

- * Likewise, when a child flow ends the outputMappings define which output attributes to map into the - * parent. In this case the subflow output attribute "attribute4" will be mapped up to the parent as "attribute3", - * updating the value of "attribute3" in the parent's flow scope. Note: only output attributes exposed by the end state - * of the ending subflow are eligible for mapping. - *

- * A FlowAttributeMapper is typically implemented using 2 distinct - * {@link org.springframework.binding.mapping.AttributeMapper} implementations: one responsible for input mapping and - * one taking care of output mapping. - *

- * Note: because FlowAttributeMappers are singletons, take care not to store and/or modify caller-specific state in a - * unsafe manner. The FlowAttributeMapper methods run in an independently executing thread on each invocation so make - * sure you deal only with local data or internal, thread-safe services. - * - * @see org.springframework.webflow.engine.SubflowState - * @see org.springframework.binding.mapping.AttributeMapper - * - * @author Keith Donald - * @author Erwin Vervaet - */ -public interface FlowAttributeMapper { - - /** - * Create a map of attributes that should be passed as input to a spawning flow. - *

- * Attributes set in the map returned by this method are availale as input to the subflow when its session is - * spawned. - * @param context the current request execution context, which gives access to the parent flow scope, the request - * scope, any event parameters, etcetera - * @return a map of attributes (name=value pairs) to pass as input to the spawning subflow - */ - public MutableAttributeMap createFlowInput(RequestContext context); - - /** - * Map output attributes of an ended flow to a resuming parent flow session. This maps the output of the - * child as new input to the resuming parent, typically adding data to flow scope. - * @param flowOutput the output attributes exposed 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); -} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/SubflowAttributeMapper.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/SubflowAttributeMapper.java new file mode 100644 index 00000000..7497cdf1 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/SubflowAttributeMapper.java @@ -0,0 +1,26 @@ +package org.springframework.webflow.engine; + +import org.springframework.webflow.core.collection.AttributeMap; +import org.springframework.webflow.core.collection.MutableAttributeMap; +import org.springframework.webflow.execution.RequestContext; + +/** + * A strategy interface used by a subflow state to map subflow input and output attributes. + * @author Keith Donald + */ +public interface SubflowAttributeMapper { + + /** + * Create a map of attributes that should be passed as input to a subflow. + * @param context the current request execution context + * @return a map of attributes to pass as input + */ + public MutableAttributeMap createFlowInput(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 context the current request execution context, which gives access to the parent flow scope + */ + public void mapFlowOutput(AttributeMap flowOutput, RequestContext context); +} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/SubflowState.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/SubflowState.java index 40047a2f..efea65ff 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/SubflowState.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/SubflowState.java @@ -15,13 +15,13 @@ */ package org.springframework.webflow.engine; +import org.springframework.binding.expression.Expression; import org.springframework.core.style.ToStringCreator; import org.springframework.util.Assert; 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.execution.FlowExecutionException; -import org.springframework.webflow.execution.RequestContext; /** * A transitionable state that spawns a subflow when executed. When the subflow this state spawns ends, the ending @@ -29,10 +29,10 @@ import org.springframework.webflow.execution.RequestContext; *

* A subflow state may be configured to map input data from its flow -- acting as the parent flow -- down to the subflow * when the subflow is spawned. In addition, output data produced by the subflow may be mapped up to the parent flow - * when the subflow ends and the parent flow resumes. See the {@link FlowAttributeMapper} interface definition for more - * information on how to do this. The logic for ending a subflow is located in the {@link EndState} implementation. + * when the subflow ends and the parent flow resumes. See the {@link SubflowAttributeMapper} interface definition for + * more information on how to do this. The logic for ending a subflow is located in the {@link EndState} implementation. * - * @see org.springframework.webflow.engine.FlowAttributeMapper + * @see org.springframework.webflow.engine.SubflowAttributeMapper * @see org.springframework.webflow.engine.EndState * * @author Keith Donald @@ -41,14 +41,14 @@ import org.springframework.webflow.execution.RequestContext; public class SubflowState extends TransitionableState { /** - * The subflow that should be spawned when this subflow state is entered. TODO - late binding + * The subflow that should be spawned when this subflow state is entered. */ - private Flow subflow; + private Expression subflow; /** * The attribute mapper that should map attributes from the parent flow down to the spawned subflow and visa versa. */ - private FlowAttributeMapper attributeMapper = new NoAttributeMapper(); + private SubflowAttributeMapper subflowAttributeMapper; /** * Create a new subflow state. @@ -56,9 +56,9 @@ public class SubflowState extends TransitionableState { * @param id the state identifier (must be unique to the flow) * @param subflow the subflow to spawn * @throws IllegalArgumentException when this state cannot be added to given flow, e.g. because the id is not unique - * @see #setAttributeMapper(FlowAttributeMapper) + * @see #setAttributeMapper(SubflowAttributeMapper) */ - public SubflowState(Flow flow, String id, Flow subflow) throws IllegalArgumentException { + public SubflowState(Flow flow, String id, Expression subflow) throws IllegalArgumentException { super(flow, id); setSubflow(subflow); } @@ -66,7 +66,7 @@ public class SubflowState extends TransitionableState { /** * Set the subflow this state will call. */ - private void setSubflow(Flow subflow) { + private void setSubflow(Expression subflow) { Assert.notNull(subflow, "A subflow state must have a subflow; the subflow is required"); this.subflow = subflow; } @@ -74,9 +74,8 @@ public class SubflowState extends TransitionableState { /** * Set the attribute mapper used to map model data between the parent and child flow. */ - public void setAttributeMapper(FlowAttributeMapper attributeMapper) { - Assert.notNull(attributeMapper, "The attribute mapper is required"); - this.attributeMapper = attributeMapper; + public void setAttributeMapper(SubflowAttributeMapper attributeMapper) { + this.subflowAttributeMapper = attributeMapper; } /** @@ -89,10 +88,17 @@ public class SubflowState extends TransitionableState { * @throws FlowExecutionException if an exception occurs in this state */ protected void doEnter(RequestControlContext context) throws FlowExecutionException { - if (logger.isDebugEnabled()) { - logger.debug("Calling subflow '" + subflow.getId() + "'"); + MutableAttributeMap flowInput; + if (subflowAttributeMapper != null) { + flowInput = subflowAttributeMapper.createFlowInput(context); + } else { + flowInput = new LocalAttributeMap(); } - context.start(subflow, attributeMapper.createFlowInput(context)); + Flow subflow = (Flow) this.subflow.getValue(context); + if (logger.isDebugEnabled()) { + logger.debug("Calling subflow '" + subflow.getId() + "' with input " + flowInput); + } + context.start(subflow, flowInput); } /** @@ -100,27 +106,19 @@ public class SubflowState extends TransitionableState { * the subflow. */ public void handleEvent(RequestControlContext context) { - attributeMapper.mapFlowOutput(context.getLastEvent().getAttributes(), context); + if (subflowAttributeMapper != null) { + AttributeMap subflowOutput = context.getLastEvent().getAttributes(); + if (logger.isDebugEnabled()) { + logger.debug("Mapping subflow output " + subflowOutput); + } + subflowAttributeMapper.mapFlowOutput(subflowOutput, context); + } super.handleEvent(context); } protected void appendToString(ToStringCreator creator) { - creator.append("subflow", subflow.getId()).append("attributeMapper", attributeMapper); + creator.append("subflow", subflow).append("subflowAttributeMapper", subflowAttributeMapper); super.appendToString(creator); } - /** - * Maps no output attributes. The default implementation. - */ - private class NoAttributeMapper implements FlowAttributeMapper { - public MutableAttributeMap createFlowInput(RequestContext context) { - logger.debug("No input will be passed to subflow"); - return new LocalAttributeMap(); - } - - public void mapFlowOutput(AttributeMap flowOutput, RequestContext context) { - logger.debug("No subflow output will be mapped"); - } - } - } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewState.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewState.java index 205541ac..50ae836e 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewState.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/ViewState.java @@ -123,6 +123,9 @@ public class ViewState extends TransitionableState { this.redirect = redirect; } + /** + * Returns whether this view state should render as a popup. + */ public boolean getPopup() { return popup; } @@ -135,6 +138,9 @@ public class ViewState extends TransitionableState { this.popup = popup; } + /** + * Returns the view factory. + */ public ViewFactory getViewFactory() { return viewFactory; } @@ -242,7 +248,8 @@ public class ViewState extends TransitionableState { protected void appendToString(ToStringCreator creator) { super.appendToString(creator); - creator.append("viewFactory", viewFactory).append("variables", variables); + creator.append("viewFactory", viewFactory).append("variables", variables).append("redirect", redirect).append( + "popup", popup); } } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/FlowArtifactFactory.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/FlowArtifactFactory.java index aa31f772..f1cbcaa4 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/FlowArtifactFactory.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/FlowArtifactFactory.java @@ -15,15 +15,16 @@ */ package org.springframework.webflow.engine.builder; +import org.springframework.binding.expression.Expression; import org.springframework.binding.mapping.AttributeMapper; import org.springframework.webflow.core.collection.AttributeMap; import org.springframework.webflow.engine.ActionState; import org.springframework.webflow.engine.DecisionState; import org.springframework.webflow.engine.EndState; import org.springframework.webflow.engine.Flow; -import org.springframework.webflow.engine.FlowAttributeMapper; import org.springframework.webflow.engine.FlowExecutionExceptionHandler; import org.springframework.webflow.engine.State; +import org.springframework.webflow.engine.SubflowAttributeMapper; import org.springframework.webflow.engine.SubflowState; import org.springframework.webflow.engine.TargetStateResolver; import org.springframework.webflow.engine.Transition; @@ -152,8 +153,8 @@ public class FlowArtifactFactory { * null * @return the fully initialized subflow state instance */ - public State createSubflowState(String id, Flow flow, Action[] entryActions, Flow subflow, - FlowAttributeMapper attributeMapper, Transition[] transitions, + public State createSubflowState(String id, Flow flow, Action[] entryActions, Expression subflow, + SubflowAttributeMapper attributeMapper, Transition[] transitions, FlowExecutionExceptionHandler[] exceptionHandlers, Action[] exitActions, AttributeMap attributes) { SubflowState subflowState = new SubflowState(flow, id, subflow); if (attributeMapper != null) { diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilder.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilder.java index fd1860aa..0e8b50bb 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilder.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilder.java @@ -32,17 +32,16 @@ import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; 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.expression.ExpressionParser; +import org.springframework.binding.expression.ParserContext; import org.springframework.binding.expression.support.CollectionAddingExpression; 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.RequiredMapping; -import org.springframework.binding.method.MethodSignature; -import org.springframework.binding.method.Parameter; -import org.springframework.binding.method.Parameters; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.support.GenericApplicationContext; @@ -58,16 +57,17 @@ import org.springframework.webflow.action.ActionResultExposer; import org.springframework.webflow.action.EvaluateAction; import org.springframework.webflow.action.ExternalRedirectAction; import org.springframework.webflow.action.FlowDefinitionRedirectAction; +import org.springframework.webflow.action.RenderAction; import org.springframework.webflow.action.SetAction; import org.springframework.webflow.action.ViewFactoryActionAdapter; 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.AnnotatedAction; +import org.springframework.webflow.definition.registry.FlowDefinitionLocator; import org.springframework.webflow.engine.Flow; -import org.springframework.webflow.engine.FlowAttributeMapper; import org.springframework.webflow.engine.FlowExecutionExceptionHandler; import org.springframework.webflow.engine.FlowVariable; +import org.springframework.webflow.engine.SubflowAttributeMapper; import org.springframework.webflow.engine.TargetStateResolver; import org.springframework.webflow.engine.Transition; import org.springframework.webflow.engine.TransitionCriteria; @@ -79,6 +79,7 @@ import org.springframework.webflow.engine.builder.support.AbstractFlowBuilder; import org.springframework.webflow.engine.builder.support.ActionExecutingViewFactory; import org.springframework.webflow.engine.support.BeanFactoryVariableValueFactory; import org.springframework.webflow.engine.support.BooleanExpressionTransitionCriteria; +import org.springframework.webflow.engine.support.GenericSubflowAttributeMapper; import org.springframework.webflow.engine.support.TransitionCriteriaChain; import org.springframework.webflow.engine.support.TransitionExecutingFlowExecutionExceptionHandler; import org.springframework.webflow.execution.Action; @@ -121,147 +122,6 @@ import org.xml.sax.SAXException; */ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolder { - // recognized XML elements and attributes - - private static final String ID_ATTRIBUTE = "id"; - - private static final String BEAN_ATTRIBUTE = "bean"; - - private static final String FLOW_ELEMENT = "flow"; - - private static final String ACTION_STATE_ELEMENT = "action-state"; - - private static final String ACTION_ELEMENT = "action"; - - private static final String NAME_ATTRIBUTE = "name"; - - private static final String METHOD_ATTRIBUTE = "method"; - - private static final String BEAN_ACTION_ELEMENT = "bean-action"; - - private static final String METHOD_ARGUMENTS_ELEMENT = "method-arguments"; - - private static final String ARGUMENT_ELEMENT = "argument"; - - private static final String EXPRESSION_ATTRIBUTE = "expression"; - - private static final String PARAMETER_TYPE_ATTRIBUTE = "parameter-type"; - - private static final String METHOD_RESULT_ELEMENT = "method-result"; - - private static final String EVALUATE_ACTION_ELEMENT = "evaluate-action"; - - private static final String SET_ELEMENT = "set"; - - private static final String ATTRIBUTE_ATTRIBUTE = "attribute"; - - private static final String EVALUATION_RESULT_ELEMENT = "evaluation-result"; - - private static final String RESULT_ATTRIBUTE = "result"; - - private static final String DEFAULT_VALUE = "default"; - - private static final String VIEW_STATE_ELEMENT = "view-state"; - - private static final String DECISION_STATE_ELEMENT = "decision-state"; - - private static final String IF_ELEMENT = "if"; - - private static final String TEST_ATTRIBUTE = "test"; - - private static final String THEN_ATTRIBUTE = "then"; - - private static final String ELSE_ATTRIBUTE = "else"; - - private static final String SUBFLOW_STATE_ELEMENT = "subflow-state"; - - private static final String FLOW_ATTRIBUTE = "flow"; - - private static final String ATTRIBUTE_MAPPER_ELEMENT = "attribute-mapper"; - - private static final String OUTPUT_MAPPER_ELEMENT = "output-mapper"; - - private static final String INPUT_MAPPER_ELEMENT = "input-mapper"; - - private static final String MAPPING_ELEMENT = "mapping"; - - private static final String SOURCE_ATTRIBUTE = "source"; - - private static final String TARGET_ATTRIBUTE = "target"; - - private static final String FROM_ATTRIBUTE = "from"; - - private static final String TO_ATTRIBUTE = "to"; - - private static final String REQUIRED_ATTRIBUTE = "required"; - - private static final String TARGET_COLLECTION_ATTRIBUTE = "target-collection"; - - private static final String END_STATE_ELEMENT = "end-state"; - - private static final String TRANSITION_ELEMENT = "transition"; - - private static final String GLOBAL_TRANSITIONS_ELEMENT = "global-transitions"; - - private static final String ON_ATTRIBUTE = "on"; - - private static final String ON_EXCEPTION_ATTRIBUTE = "on-exception"; - - private static final String ATTRIBUTE_ELEMENT = "attribute"; - - private static final String TYPE_ATTRIBUTE = "type"; - - private static final String VALUE_ELEMENT = "value"; - - private static final String VALUE_ATTRIBUTE = "value"; - - private static final String VAR_ELEMENT = "var"; - - private static final String SCOPE_ATTRIBUTE = "scope"; - - private static final String CLASS_ATTRIBUTE = "class"; - - private static final String START_ACTIONS_ELEMENT = "start-actions"; - - private static final String END_ACTIONS_ELEMENT = "end-actions"; - - private static final String ENTRY_ACTIONS_ELEMENT = "entry-actions"; - - private static final String RENDER_ACTIONS_ELEMENT = "render-actions"; - - private static final String EXIT_ACTIONS_ELEMENT = "exit-actions"; - - private static final String EXCEPTION_HANDLER_ELEMENT = "exception-handler"; - - private static final String IMPORT_ELEMENT = "import"; - - private static final String RESOURCE_ATTRIBUTE = "resource"; - - private static final String VIEW_ATTRIBUTE = "view"; - - private static final String SECURED_ELEMENT = "secured"; - - private static final String AUTHORITIES_ATTRIBUTE = "authorities"; - - private static final String MATCH_ATTRIBUTE = "match"; - - /** - * Prefix used when the encoded view name wants to specify that a redirect to an external URL is required. - * ("externalRedirect:") - */ - private static final String EXTERNAL_REDIRECT_PREFIX = "externalRedirect:"; - - /** - * Prefix used when the encoded view name wants to specify that a redirect to a flow definition is requred. - * ("flowRedirect:") - */ - private static final String FLOW_DEFINITION_REDIRECT_PREFIX = "flowRedirect:"; - - /** - * Prefix used when the user wants to use a ViewSelector implementation managed by a bean factory. ("bean:") - */ - private static final String BEAN_PREFIX = "bean:"; - /** * The resource from which the document element being parsed was read. Used as a location for relative resource * lookup. @@ -332,7 +192,7 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde } public void buildInputMapper() throws FlowBuilderException { - AttributeMapper inputMapper = parseInputMapper(getDocumentElement()); + AttributeMapper inputMapper = parseInputMapper(getDocumentElement(), AttributeMap.class, RequestContext.class); if (inputMapper != null) { getFlow().setInputMapper(inputMapper); } @@ -355,7 +215,8 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde } public void buildOutputMapper() throws FlowBuilderException { - AttributeMapper outputMapper = parseOutputMapper(getDocumentElement()); + AttributeMapper outputMapper = parseOutputMapper(getDocumentElement(), RequestContext.class, + MutableAttributeMap.class); if (outputMapper != null) { getFlow().setOutputMapper(outputMapper); } @@ -408,17 +269,21 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde // internal parsing logic and hook methods private Flow parseFlow(Element flowElement) { - if (!isFlowElement(flowElement)) { - throw new IllegalArgumentException("This is not the '" + FLOW_ELEMENT + "' element"); + if (!isRootFlowElement(flowElement)) { + throw new IllegalArgumentException("This is not the root 'flow' element"); } String flowId = getLocalContext().getFlowId(); AttributeMap externallyAssignedAttributes = getLocalContext().getFlowAttributes(); - MutableAttributeMap flowAttributes = parseAttributes(flowElement); + MutableAttributeMap flowAttributes = parseMetaAttributes(flowElement); parseAndSetPersistenceContextAttribute(flowElement, flowAttributes); parseAndSetSecuredAttribute(flowElement, flowAttributes); return getFlowArtifactFactory().createFlow(flowId, flowAttributes.union(externallyAssignedAttributes)); } + private boolean isRootFlowElement(Element flowElement) { + return DomUtils.nodeNameEquals(flowElement, "flow"); + } + private void parseAndSetPersistenceContextAttribute(Element flowElement, MutableAttributeMap flowAttributes) { Element element = DomUtils.getChildElementByTagName(flowElement, "persistence-context"); if (element != null) { @@ -426,20 +291,16 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde } } - private boolean isFlowElement(Element flowElement) { - return DomUtils.nodeNameEquals(flowElement, FLOW_ELEMENT); - } - private void initLocalFlowContext(Element flowElement) { - List importElements = DomUtils.getChildElementsByTagName(flowElement, IMPORT_ELEMENT); + List importElements = DomUtils.getChildElementsByTagName(flowElement, "import"); Resource[] resources = new Resource[importElements.size()]; for (int i = 0; i < importElements.size(); i++) { Element importElement = (Element) importElements.get(i); try { - resources[i] = getResource().createRelative(importElement.getAttribute(RESOURCE_ATTRIBUTE)); + resources[i] = getResource().createRelative(importElement.getAttribute("resource")); } catch (IOException e) { throw new FlowBuilderException("Could not access flow-relative artifact resource '" - + importElement.getAttribute(RESOURCE_ATTRIBUTE) + "'", e); + + importElement.getAttribute("resource") + "'", e); } } this.localFlowBuilderContext = new LocalFlowBuilderContext(getContext(), @@ -486,17 +347,17 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde } private void parseAndAddFlowVariables(Element flowElement, Flow flow) { - List varElements = DomUtils.getChildElementsByTagName(flowElement, VAR_ELEMENT); + List varElements = DomUtils.getChildElementsByTagName(flowElement, "var"); for (Iterator it = varElements.iterator(); it.hasNext();) { flow.addVariable(parseFlowVariable((Element) it.next())); } } private FlowVariable parseFlowVariable(Element element) { - Class clazz = (Class) fromStringTo(Class.class).execute(element.getAttribute(CLASS_ATTRIBUTE)); + Class clazz = (Class) fromStringTo(Class.class).execute(element.getAttribute("class")); VariableValueFactory valueFactory = new BeanFactoryVariableValueFactory(clazz, (AutowireCapableBeanFactory) getFlow().getBeanFactory()); - ScopeType scope = parseScope(element, ScopeType.FLOW); + ScopeType scope = parseScopeAttribute(element, ScopeType.FLOW); if (!(scope == ScopeType.FLOW || scope == ScopeType.CONVERSATION)) { throw new IllegalArgumentException("Only " + ScopeType.FLOW + " or " + ScopeType.CONVERSATION + " scope is allowed for flow variables"); @@ -504,22 +365,30 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde return new FlowVariable(element.getAttribute("name"), valueFactory, scope == ScopeType.FLOW ? true : false); } + private ScopeType parseScopeAttribute(Element element, ScopeType defaultScope) { + if (element.hasAttribute("scope")) { + return (ScopeType) fromStringTo(ScopeType.class).execute(element.getAttribute("scope")); + } else { + return defaultScope; + } + } + private void parseAndAddStartActions(Element element, Flow flow) { - Element startElement = DomUtils.getChildElementByTagName(element, START_ACTIONS_ELEMENT); + Element startElement = DomUtils.getChildElementByTagName(element, "start-actions"); if (startElement != null) { - flow.getStartActionList().addAll(parseAnnotatedActions(startElement)); + flow.getStartActionList().addAll(parseActions(startElement)); } } private void parseAndAddEndActions(Element element, Flow flow) { - Element endElement = DomUtils.getChildElementByTagName(element, END_ACTIONS_ELEMENT); + Element endElement = DomUtils.getChildElementByTagName(element, "end-actions"); if (endElement != null) { - flow.getEndActionList().addAll(parseAnnotatedActions(endElement)); + flow.getEndActionList().addAll(parseActions(endElement)); } } private void parseAndAddGlobalTransitions(Element element, Flow flow) { - Element globalTransitionsElement = DomUtils.getChildElementByTagName(element, GLOBAL_TRANSITIONS_ELEMENT); + Element globalTransitionsElement = DomUtils.getChildElementByTagName(element, "global-transitions"); if (globalTransitionsElement != null) { flow.getGlobalTransitionSet().addAll(parseTransitions(globalTransitionsElement)); } @@ -531,15 +400,15 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde Node childNode = childNodeList.item(i); if (childNode instanceof Element) { Element stateElement = (Element) childNode; - if (DomUtils.nodeNameEquals(stateElement, ACTION_STATE_ELEMENT)) { + if (DomUtils.nodeNameEquals(stateElement, "action-state")) { parseAndAddActionState(stateElement, flow); - } else if (DomUtils.nodeNameEquals(stateElement, VIEW_STATE_ELEMENT)) { + } else if (DomUtils.nodeNameEquals(stateElement, "view-state")) { parseAndAddViewState(stateElement, flow); - } else if (DomUtils.nodeNameEquals(stateElement, DECISION_STATE_ELEMENT)) { + } else if (DomUtils.nodeNameEquals(stateElement, "decision-state")) { parseAndAddDecisionState(stateElement, flow); - } else if (DomUtils.nodeNameEquals(stateElement, SUBFLOW_STATE_ELEMENT)) { + } else if (DomUtils.nodeNameEquals(stateElement, "subflow-state")) { parseAndAddSubflowState(stateElement, flow); - } else if (DomUtils.nodeNameEquals(stateElement, END_STATE_ELEMENT)) { + } else if (DomUtils.nodeNameEquals(stateElement, "end-state")) { parseAndAddEndState(stateElement, flow); } } @@ -564,10 +433,10 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde } private void parseAndAddActionState(Element element, Flow flow) { - MutableAttributeMap attributes = parseAttributes(element); + MutableAttributeMap attributes = parseMetaAttributes(element); parseAndSetSecuredAttribute(element, attributes); getFlowArtifactFactory().createActionState(parseId(element), flow, parseEntryActions(element), - parseAnnotatedActions(element), parseTransitions(element), parseExceptionHandlers(element), + parseActions(element), parseTransitions(element), parseExceptionHandlers(element), parseExitActions(element), attributes); } @@ -581,7 +450,7 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde if (element.hasAttribute("popup")) { popup = ((Boolean) fromStringTo(Boolean.class).execute(element.getAttribute("popup"))).booleanValue(); } - MutableAttributeMap attributes = parseAttributes(element); + MutableAttributeMap attributes = parseMetaAttributes(element); parseAndSetSecuredAttribute(element, attributes); getFlowArtifactFactory().createViewState(parseId(element), flow, parseViewVariables(element), parseEntryActions(element), viewFactory, redirect, popup, parseRenderActions(element), @@ -589,34 +458,66 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde } private void parseAndAddDecisionState(Element element, Flow flow) { - MutableAttributeMap attributes = parseAttributes(element); + MutableAttributeMap attributes = parseMetaAttributes(element); parseAndSetSecuredAttribute(element, attributes); getFlowArtifactFactory().createDecisionState(parseId(element), flow, parseEntryActions(element), parseIfs(element), parseExceptionHandlers(element), parseExitActions(element), attributes); } private void parseAndAddSubflowState(Element element, Flow flow) { - MutableAttributeMap attributes = parseAttributes(element); + MutableAttributeMap attributes = parseMetaAttributes(element); parseAndSetSecuredAttribute(element, attributes); getFlowArtifactFactory().createSubflowState(parseId(element), flow, parseEntryActions(element), - parseSubflow(element), parseFlowAttributeMapper(element), parseTransitions(element), + parseSubflowExpression(element), parseSubflowAttributeMapper(element), parseTransitions(element), parseExceptionHandlers(element), parseExitActions(element), attributes); } + private Expression parseSubflowExpression(Element element) { + String subflow = element.getAttribute("subflow"); + Expression subflowId = getExpressionParser().parseExpression(subflow, + new ParserContextImpl().eval(RequestContext.class).expect(String.class)); + return new SubflowExpression(subflowId, getLocalContext().getFlowDefinitionLocator()); + } + + private static class SubflowExpression implements Expression { + + private Expression subflowId; + + private FlowDefinitionLocator flowDefinitionLocator; + + public SubflowExpression(Expression subflowId, FlowDefinitionLocator flowDefinitionLocator) { + this.subflowId = subflowId; + this.flowDefinitionLocator = flowDefinitionLocator; + } + + public Object getValue(Object context) throws EvaluationException { + String subflowId = (String) this.subflowId.getValue(context); + return flowDefinitionLocator.getFlowDefinition(subflowId); + } + + public void setValue(Object context, Object value) throws EvaluationException { + throw new UnsupportedOperationException("Cannot set a subflow expression"); + } + } + private void parseAndAddEndState(Element element, Flow flow) { - MutableAttributeMap attributes = parseAttributes(element); + MutableAttributeMap attributes = parseMetaAttributes(element); + if (element.hasAttribute("commit")) { + attributes.put("commit", fromStringTo(Boolean.class).execute(element.getAttribute("commit"))); + } parseAndSetSecuredAttribute(element, attributes); getFlowArtifactFactory().createEndState(parseId(element), flow, parseEntryActions(element), - new ViewFactoryActionAdapter(parseViewFactory(element, true)), parseOutputMapper(element), + new ViewFactoryActionAdapter(parseViewFactory(element, true)), + parseOutputMapper(element, RequestContext.class, MutableAttributeMap.class), parseExceptionHandlers(element), attributes); } private String parseId(Element element) { - return element.getAttribute(ID_ATTRIBUTE); + return element.getAttribute("id"); } private ViewVariable[] parseViewVariables(Element viewStateElement) { - List varElements = DomUtils.getChildElementsByTagName(viewStateElement, VAR_ELEMENT); + List varElements = DomUtils.getChildElementsByTagName(viewStateElement, "var"); List variables = new ArrayList(varElements.size()); for (Iterator it = varElements.iterator(); it.hasNext();) { variables.add(parseViewVariable((Element) it.next())); @@ -625,46 +526,46 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde } private ViewVariable parseViewVariable(Element element) { - Class clazz = (Class) fromStringTo(Class.class).execute(element.getAttribute(CLASS_ATTRIBUTE)); + Class clazz = (Class) fromStringTo(Class.class).execute(element.getAttribute("class")); VariableValueFactory valueFactory = new BeanFactoryVariableValueFactory(clazz, (AutowireCapableBeanFactory) getFlow().getBeanFactory()); return new ViewVariable(element.getAttribute("name"), valueFactory); } private Action[] parseEntryActions(Element element) { - Element entryActionsElement = DomUtils.getChildElementByTagName(element, ENTRY_ACTIONS_ELEMENT); + Element entryActionsElement = DomUtils.getChildElementByTagName(element, "entry-actions"); if (entryActionsElement != null) { - return parseAnnotatedActions(entryActionsElement); + return parseActions(entryActionsElement); } else { return null; } } private ViewFactory parseViewFactory(Element element, boolean endState) { - String encodedView = element.getAttribute(VIEW_ATTRIBUTE); + String encodedView = element.getAttribute("view"); if (!StringUtils.hasText(encodedView)) { if (endState) { return null; } else { - encodedView = createViewId(element.getAttribute(ID_ATTRIBUTE)); + encodedView = createViewId(parseId(element)); Expression viewName = getExpressionParser().parseExpression(encodedView, new ParserContextImpl().eval(RequestContext.class).expect(String.class)); return getLocalContext().getViewFactoryCreator().createViewFactory(viewName, getLocalContext().getResourceLoader()); } - } else if (encodedView.startsWith(EXTERNAL_REDIRECT_PREFIX)) { - String encodedUrl = encodedView.substring(EXTERNAL_REDIRECT_PREFIX.length()); + } else if (encodedView.startsWith("externalRedirect:")) { + String encodedUrl = encodedView.substring("externalRedirect:".length()); Expression externalUrl = getExpressionParser().parseExpression(encodedUrl, new ParserContextImpl().eval(RequestContext.class).expect(String.class)); return new ActionExecutingViewFactory(new ExternalRedirectAction(externalUrl)); - } else if (encodedView.startsWith(FLOW_DEFINITION_REDIRECT_PREFIX)) { - String flowRedirect = encodedView.substring(FLOW_DEFINITION_REDIRECT_PREFIX.length()); + } else if (encodedView.startsWith("flowRedirect:")) { + String flowRedirect = encodedView.substring("flowRedirect:".length()); Expression expression = getExpressionParser().parseExpression(flowRedirect, new ParserContextImpl().eval(RequestContext.class).expect(String.class)); return new ActionExecutingViewFactory(new FlowDefinitionRedirectAction(expression)); - } else if (encodedView.startsWith(BEAN_PREFIX)) { - return (ViewFactory) getLocalContext().getBeanFactory().getBean( - encodedView.substring(BEAN_PREFIX.length()), ViewFactory.class); + } else if (encodedView.startsWith("bean:")) { + return (ViewFactory) getLocalContext().getBeanFactory().getBean(encodedView.substring("bean:".length()), + ViewFactory.class); } else { Expression viewName = getExpressionParser().parseExpression(encodedView, new ParserContextImpl().eval(RequestContext.class).expect(String.class)); @@ -679,18 +580,18 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde } private Action[] parseRenderActions(Element element) { - Element renderActionsElement = DomUtils.getChildElementByTagName(element, RENDER_ACTIONS_ELEMENT); + Element renderActionsElement = DomUtils.getChildElementByTagName(element, "render-actions"); if (renderActionsElement != null) { - return parseAnnotatedActions(renderActionsElement); + return parseActions(renderActionsElement); } else { return null; } } private Action[] parseExitActions(Element element) { - Element exitActionsElement = DomUtils.getChildElementByTagName(element, EXIT_ACTIONS_ELEMENT); + Element exitActionsElement = DomUtils.getChildElementByTagName(element, "exit-actions"); if (exitActionsElement != null) { - return parseAnnotatedActions(exitActionsElement); + return parseActions(exitActionsElement); } else { return null; } @@ -698,10 +599,10 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde private Transition[] parseTransitions(Element element) { List transitions = new LinkedList(); - List transitionElements = DomUtils.getChildElementsByTagName(element, TRANSITION_ELEMENT); + List transitionElements = DomUtils.getChildElementsByTagName(element, "transition"); for (Iterator it = transitionElements.iterator(); it.hasNext();) { Element transitionElement = (Element) it.next(); - if (!StringUtils.hasText(transitionElement.getAttribute(ON_EXCEPTION_ATTRIBUTE))) { + if (!StringUtils.hasText(transitionElement.getAttribute("on-exception"))) { transitions.add(parseTransition(transitionElement)); } } @@ -710,22 +611,17 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde private Transition parseTransition(Element element) { TransitionCriteria matchingCriteria = (TransitionCriteria) fromStringTo(TransitionCriteria.class).execute( - element.getAttribute(ON_ATTRIBUTE)); + element.getAttribute("on")); TargetStateResolver targetStateResolver = (TargetStateResolver) fromStringTo(TargetStateResolver.class) - .execute(element.getAttribute(TO_ATTRIBUTE)); - TransitionCriteria executionCriteria = TransitionCriteriaChain.criteriaChainFor(parseAnnotatedActions(element)); - MutableAttributeMap attributes = parseAttributes(element); + .execute(element.getAttribute("to")); + TransitionCriteria executionCriteria = TransitionCriteriaChain.criteriaChainFor(parseActions(element)); + MutableAttributeMap attributes = parseMetaAttributes(element); parseAndSetSecuredAttribute(element, attributes); return getFlowArtifactFactory().createTransition(targetStateResolver, matchingCriteria, executionCriteria, attributes); } - private Flow parseSubflow(Element element) { - return (Flow) getLocalContext().getFlowDefinitionLocator().getFlowDefinition( - element.getAttribute(FLOW_ATTRIBUTE)); - } - - private AnnotatedAction[] parseAnnotatedActions(Element element) { + private Action[] parseActions(Element element) { List actions = new LinkedList(); NodeList childNodeList = element.getChildNodes(); for (int i = 0; i < childNodeList.getLength(); i++) { @@ -733,201 +629,87 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde if (!(childNode instanceof Element)) { continue; } - if (DomUtils.nodeNameEquals(childNode, ACTION_ELEMENT)) { - actions.add(parseAnnotatedAction((Element) childNode)); - } else if (DomUtils.nodeNameEquals(childNode, BEAN_ACTION_ELEMENT)) { - actions.add(parseAnnotatedBeanInvokingAction((Element) childNode)); - } else if (DomUtils.nodeNameEquals(childNode, EVALUATE_ACTION_ELEMENT)) { - actions.add(parseAnnotatedEvaluateAction((Element) childNode)); - } else if (DomUtils.nodeNameEquals(childNode, SET_ELEMENT)) { - actions.add(parseAnnotatedSetAction((Element) childNode)); + if (DomUtils.nodeNameEquals(childNode, "evaluate")) { + actions.add(parseEvaluateAction((Element) childNode)); + } else if (DomUtils.nodeNameEquals(childNode, "render")) { + actions.add(parseRenderAction((Element) childNode)); + } else if (DomUtils.nodeNameEquals(childNode, "set")) { + actions.add(parseSetAction((Element) childNode)); } } - return (AnnotatedAction[]) actions.toArray(new AnnotatedAction[actions.size()]); - } - - private AnnotatedAction parseAnnotatedAction(Element element) { - AnnotatedAction annotated = new AnnotatedAction(parseAction(element)); - parseCommonProperties(element, annotated); - if (element.hasAttribute(METHOD_ATTRIBUTE)) { - annotated.setMethod(element.getAttribute(METHOD_ATTRIBUTE)); - } - return annotated; - } - - private Action parseAction(Element element) { - String actionId = element.getAttribute(BEAN_ATTRIBUTE); - return (Action) getLocalContext().getBeanFactory().getBean(actionId, Action.class); - } - - private AnnotatedAction parseCommonProperties(Element element, AnnotatedAction annotated) { - if (element.hasAttribute(NAME_ATTRIBUTE)) { - annotated.setName(element.getAttribute(NAME_ATTRIBUTE)); - } - annotated.getAttributeMap().putAll(parseAttributes(element)); - return annotated; - } - - private AnnotatedAction parseAnnotatedBeanInvokingAction(Element element) { - AnnotatedAction annotated = new AnnotatedAction(parseBeanInvokingAction(element)); - return parseCommonProperties(element, annotated); - } - - private Action parseBeanInvokingAction(Element element) { - String beanId = element.getAttribute(BEAN_ATTRIBUTE); - String methodName = element.getAttribute(METHOD_ATTRIBUTE); - Parameters parameters = parseMethodParameters(element); - MethodSignature methodSignature = new MethodSignature(methodName, parameters); - ActionResultExposer resultExposer = parseMethodResultExposer(element); - return getLocalContext().getBeanInvokingActionFactory().createBeanInvokingAction(beanId, - getLocalContext().getBeanFactory(), methodSignature, resultExposer, - getLocalContext().getConversionService(), null); - } - - private Parameters parseMethodParameters(Element element) { - Element methodArgumentsElement = DomUtils.getChildElementByTagName(element, METHOD_ARGUMENTS_ELEMENT); - if (methodArgumentsElement == null) { - return Parameters.NONE; - } - Parameters parameters = new Parameters(); - Iterator it = DomUtils.getChildElementsByTagName(methodArgumentsElement, ARGUMENT_ELEMENT).iterator(); - ExpressionParser parser = getLocalContext().getExpressionParser(); - while (it.hasNext()) { - Element argumentElement = (Element) it.next(); - Expression name = parser.parseExpression(argumentElement.getAttribute(EXPRESSION_ATTRIBUTE), - new ParserContextImpl().eval(RequestContext.class)); - Class type = null; - if (argumentElement.hasAttribute(PARAMETER_TYPE_ATTRIBUTE)) { - type = (Class) fromStringTo(Class.class) - .execute(argumentElement.getAttribute(PARAMETER_TYPE_ATTRIBUTE)); - } - parameters.add(new Parameter(type, name)); - } - return parameters; - } - - private Class parseResultType(Element resultElement) { - Class type = null; - if (resultElement != null) { - if (resultElement.hasAttribute(TYPE_ATTRIBUTE)) { - type = (Class) fromStringTo(Class.class).execute(resultElement.getAttribute(TYPE_ATTRIBUTE)); - } - } - return type; - } - - private ActionResultExposer parseMethodResultExposer(Element element) { - Element resultElement = DomUtils.getChildElementByTagName(element, METHOD_RESULT_ELEMENT); - if (resultElement != null) { - return parseActionResultExposer(resultElement); - } else { - return null; - } - } - - private ActionResultExposer parseActionResultExposer(Element element) { - String nameExpressionString = element.getAttribute(NAME_ATTRIBUTE); - ScopeType scope = parseScope(element, null); - Expression nameExpression; - if (scope != null) { - nameExpression = getExpressionParser().parseExpression(nameExpressionString, - new ParserContextImpl().eval(MutableAttributeMap.class)); - } else { - nameExpression = getExpressionParser().parseExpression(nameExpressionString, - new ParserContextImpl().eval(RequestContext.class)); - } - ActionResultExposer exposer = new ActionResultExposer(nameExpression, scope, parseResultType(element)); - exposer.setConversionService(getLocalContext().getConversionService()); - return exposer; - } - - private AnnotatedAction parseAnnotatedEvaluateAction(Element element) { - AnnotatedAction annotated = new AnnotatedAction(parseEvaluateAction(element)); - return parseCommonProperties(element, annotated); + return (Action[]) actions.toArray(new Action[actions.size()]); } private Action parseEvaluateAction(Element element) { - Expression expression = getExpressionParser().parseExpression(element.getAttribute(EXPRESSION_ATTRIBUTE), + String expressionString = element.getAttribute("expression"); + Expression expression = getExpressionParser().parseExpression(expressionString, new ParserContextImpl().eval(RequestContext.class)); - return new EvaluateAction(expression, parseEvaluationResultExposer(element)); + return new EvaluateAction(expression, parseEvaluationActionResultExposer(element)); } - private ExpressionParser getExpressionParser() { - return getLocalContext().getExpressionParser(); - } - - private ActionResultExposer parseEvaluationResultExposer(Element element) { - Element resultElement = DomUtils.getChildElementByTagName(element, EVALUATION_RESULT_ELEMENT); - if (resultElement != null) { - return parseActionResultExposer(resultElement); - } else if (element.hasAttribute(RESULT_ATTRIBUTE)) { - String resultExpressionString = element.getAttribute(RESULT_ATTRIBUTE); + private ActionResultExposer parseEvaluationActionResultExposer(Element element) { + if (element.hasAttribute("result")) { + String resultExpressionString = element.getAttribute("result"); Expression resultExpression = getExpressionParser().parseExpression(resultExpressionString, new ParserContextImpl().eval(RequestContext.class)); - - ActionResultExposer exposer = new ActionResultExposer(resultExpression, null, parseResultType(element)); - exposer.setConversionService(getLocalContext().getConversionService()); - return exposer; + Class expectedResultType = null; + if (element.hasAttribute("result-type")) { + expectedResultType = (Class) fromStringTo(Class.class).execute(element.getAttribute("result-type")); + } + return new ActionResultExposer(resultExpression, expectedResultType, getConversionService()); } else { return null; } } - private AnnotatedAction parseAnnotatedSetAction(Element element) { - AnnotatedAction annotated = new AnnotatedAction(parseSetAction(element)); - return parseCommonProperties(element, annotated); + private Action parseRenderAction(Element element) { + String[] fragmentExpressionStrings = StringUtils.commaDelimitedListToStringArray(element + .getAttribute("fragments")); + fragmentExpressionStrings = StringUtils.trimArrayElements(fragmentExpressionStrings); + ExpressionParser parser = getExpressionParser(); + ParserContext context = new ParserContextImpl().eval(RequestContext.class).expect(String.class); + Expression[] fragments = new Expression[fragmentExpressionStrings.length]; + for (int i = 0; i < fragmentExpressionStrings.length; i++) { + String fragment = fragmentExpressionStrings[i]; + fragments[i] = parser.parseExpression(fragment, context); + } + return new RenderAction(fragments); } private Action parseSetAction(Element element) { - ScopeType scope = parseScope(element, null); - String attributeExpressionString = element.getAttribute(ATTRIBUTE_ATTRIBUTE); - Expression attributeExpression; - if (scope != null) { - attributeExpression = getExpressionParser().parseExpression(attributeExpressionString, - new ParserContextImpl().eval(MutableAttributeMap.class)); - } else { - attributeExpression = getExpressionParser().parseExpression(attributeExpressionString, - new ParserContextImpl().eval(RequestContext.class)); - } - - Expression valueExpression = getExpressionParser().parseExpression(element.getAttribute(VALUE_ATTRIBUTE), + String nameExpressionString = element.getAttribute("name"); + Expression nameExpression = getExpressionParser().parseExpression(nameExpressionString, new ParserContextImpl().eval(RequestContext.class)); - return new SetAction(attributeExpression, scope, valueExpression); + Expression valueExpression = getExpressionParser().parseExpression("value", + new ParserContextImpl().eval(RequestContext.class)); + return new SetAction(nameExpression, valueExpression); } - private ScopeType parseScope(Element element, ScopeType defaultValue) { - if (element.hasAttribute(SCOPE_ATTRIBUTE) && !element.getAttribute(SCOPE_ATTRIBUTE).equals(DEFAULT_VALUE)) { - return (ScopeType) fromStringTo(ScopeType.class).execute(element.getAttribute(SCOPE_ATTRIBUTE)); - } else { - return defaultValue; - } - } - - private MutableAttributeMap parseAttributes(Element element) { + private MutableAttributeMap parseMetaAttributes(Element element) { LocalAttributeMap attributes = new LocalAttributeMap(); - List propertyElements = DomUtils.getChildElementsByTagName(element, ATTRIBUTE_ELEMENT); + List propertyElements = DomUtils.getChildElementsByTagName(element, "attribute"); for (int i = 0; i < propertyElements.size(); i++) { - parseAndSetAttribute((Element) propertyElements.get(i), attributes); + parseAndSetMetaAttribute((Element) propertyElements.get(i), attributes); } return attributes; } - private void parseAndSetAttribute(Element element, MutableAttributeMap attributes) { - String name = element.getAttribute(NAME_ATTRIBUTE); + private void parseAndSetMetaAttribute(Element element, MutableAttributeMap attributes) { + String name = element.getAttribute("name"); String value = null; - if (element.hasAttribute(VALUE_ATTRIBUTE)) { - value = element.getAttribute(VALUE_ATTRIBUTE); + if (element.hasAttribute("value")) { + value = element.getAttribute("value"); } else { - List valueElements = DomUtils.getChildElementsByTagName(element, VALUE_ELEMENT); + List valueElements = DomUtils.getChildElementsByTagName(element, "value"); Assert.state(valueElements.size() == 1, "A property value should be specified for property '" + name + "'"); value = DomUtils.getTextValue((Element) valueElements.get(0)); } - attributes.put(name, convertPropertyValue(element, value)); + attributes.put(name, convertAttributeValueIfNecessary(element, value)); } - private Object convertPropertyValue(Element element, String stringValue) { - if (element.hasAttribute(TYPE_ATTRIBUTE)) { - Class targetClass = (Class) fromStringTo(Class.class).execute(element.getAttribute(TYPE_ATTRIBUTE)); + private Object convertAttributeValueIfNecessary(Element element, String stringValue) { + if (element.hasAttribute("type")) { + Class targetClass = (Class) fromStringTo(Class.class).execute(element.getAttribute("type")); return fromStringTo(targetClass).execute(stringValue); } else { return stringValue; @@ -936,7 +718,7 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde private Transition[] parseIfs(Element element) { List transitions = new LinkedList(); - List transitionElements = DomUtils.getChildElementsByTagName(element, IF_ELEMENT); + List transitionElements = DomUtils.getChildElementsByTagName(element, "if"); for (Iterator it = transitionElements.iterator(); it.hasNext();) { transitions.addAll(Arrays.asList(parseIf((Element) it.next()))); } @@ -945,7 +727,7 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde private Transition[] parseIf(Element element) { Transition thenTransition = parseThen(element); - if (StringUtils.hasText(element.getAttribute(ELSE_ATTRIBUTE))) { + if (StringUtils.hasText(element.getAttribute("else"))) { Transition elseTransition = parseElse(element); return new Transition[] { thenTransition, elseTransition }; } else { @@ -954,72 +736,48 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde } private Transition parseThen(Element element) { - Expression expression = getExpressionParser().parseExpression(element.getAttribute(TEST_ATTRIBUTE), + Expression expression = getExpressionParser().parseExpression(element.getAttribute("test"), new ParserContextImpl().eval(RequestContext.class).expect(Boolean.class)); TransitionCriteria matchingCriteria = new BooleanExpressionTransitionCriteria(expression); TargetStateResolver targetStateResolver = (TargetStateResolver) fromStringTo(TargetStateResolver.class) - .execute(element.getAttribute(THEN_ATTRIBUTE)); + .execute(element.getAttribute("then")); return getFlowArtifactFactory().createTransition(targetStateResolver, matchingCriteria, null, null); } private Transition parseElse(Element element) { TargetStateResolver targetStateResolver = (TargetStateResolver) fromStringTo(TargetStateResolver.class) - .execute(element.getAttribute(ELSE_ATTRIBUTE)); + .execute(element.getAttribute("else")); return getFlowArtifactFactory().createTransition(targetStateResolver, null, null, null); } - private FlowAttributeMapper parseFlowAttributeMapper(Element element) { - Element mapperElement = DomUtils.getChildElementByTagName(element, ATTRIBUTE_MAPPER_ELEMENT); - if (mapperElement == null) { - return null; - } - if (StringUtils.hasText(mapperElement.getAttribute(BEAN_ATTRIBUTE))) { - return (FlowAttributeMapper) getLocalContext().getBeanFactory().getBean( - mapperElement.getAttribute(BEAN_ATTRIBUTE), FlowAttributeMapper.class); + private SubflowAttributeMapper parseSubflowAttributeMapper(Element element) { + if (element.hasAttribute("subflow-attribute-mapper")) { + String attributeMapperBeanId = element.getAttribute("subflow-attribute-mapper"); + return (SubflowAttributeMapper) getLocalContext().getBeanFactory().getBean(attributeMapperBeanId, + SubflowAttributeMapper.class); } else { - return new ImmutableFlowAttributeMapper(parseSubflowInputMapper(mapperElement), - parseSubflowOutputMapper(mapperElement)); + AttributeMapper inputMapper = parseInputMapper(element, RequestContext.class, MutableAttributeMap.class); + AttributeMapper outputMapper = parseOutputMapper(element, AttributeMap.class, RequestContext.class); + return new GenericSubflowAttributeMapper(inputMapper, outputMapper); } } - private AttributeMapper parseInputMapper(Element element) { - Element mapperElement = DomUtils.getChildElementByTagName(element, INPUT_MAPPER_ELEMENT); + private AttributeMapper parseInputMapper(Element element, Class sourceType, Class targetType) { + Element mapperElement = DomUtils.getChildElementByTagName(element, "input-mapper"); if (mapperElement != null) { DefaultAttributeMapper mapper = new DefaultAttributeMapper(); - parseMappings(mapper, mapperElement, MutableAttributeMap.class, RequestContext.class); + parseMappings(mapper, mapperElement, sourceType, targetType); return mapper; } else { return null; } } - private AttributeMapper parseSubflowInputMapper(Element element) { - Element mapperElement = DomUtils.getChildElementByTagName(element, INPUT_MAPPER_ELEMENT); + private AttributeMapper parseOutputMapper(Element element, Class sourceType, Class targetType) { + Element mapperElement = DomUtils.getChildElementByTagName(element, "output-mapper"); if (mapperElement != null) { DefaultAttributeMapper mapper = new DefaultAttributeMapper(); - parseMappings(mapper, mapperElement, RequestContext.class, MutableAttributeMap.class); - return mapper; - } else { - return null; - } - } - - private AttributeMapper parseOutputMapper(Element element) { - Element mapperElement = DomUtils.getChildElementByTagName(element, OUTPUT_MAPPER_ELEMENT); - if (mapperElement != null) { - DefaultAttributeMapper mapper = new DefaultAttributeMapper(); - parseMappings(mapper, mapperElement, RequestContext.class, MutableAttributeMap.class); - return mapper; - } else { - return null; - } - } - - private AttributeMapper parseSubflowOutputMapper(Element element) { - Element mapperElement = DomUtils.getChildElementByTagName(element, OUTPUT_MAPPER_ELEMENT); - if (mapperElement != null) { - DefaultAttributeMapper mapper = new DefaultAttributeMapper(); - parseMappings(mapper, mapperElement, MutableAttributeMap.class, RequestContext.class); + parseMappings(mapper, mapperElement, sourceType, targetType); return mapper; } else { return null; @@ -1028,20 +786,20 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde private void parseMappings(DefaultAttributeMapper mapper, Element element, Class sourceClass, Class targetClass) { ExpressionParser parser = getLocalContext().getExpressionParser(); - List mappingElements = DomUtils.getChildElementsByTagName(element, MAPPING_ELEMENT); + List mappingElements = DomUtils.getChildElementsByTagName(element, "mapping"); for (Iterator it = mappingElements.iterator(); it.hasNext();) { Element mappingElement = (Element) it.next(); - Expression source = parser.parseExpression(mappingElement.getAttribute(SOURCE_ATTRIBUTE), - new ParserContextImpl().eval(sourceClass)); + Expression source = parser.parseExpression(mappingElement.getAttribute("source"), new ParserContextImpl() + .eval(sourceClass)); Expression target = null; - if (StringUtils.hasText(mappingElement.getAttribute(TARGET_ATTRIBUTE))) { - target = parser.parseExpression(mappingElement.getAttribute(TARGET_ATTRIBUTE), new ParserContextImpl() + if (StringUtils.hasText(mappingElement.getAttribute("target"))) { + target = parser.parseExpression(mappingElement.getAttribute("target"), new ParserContextImpl() .eval(targetClass)); - } else if (StringUtils.hasText(mappingElement.getAttribute(TARGET_COLLECTION_ATTRIBUTE))) { + } else if (StringUtils.hasText(mappingElement.getAttribute("target-collection"))) { target = new CollectionAddingExpression(parser.parseExpression(mappingElement - .getAttribute(TARGET_COLLECTION_ATTRIBUTE), new ParserContextImpl().eval(targetClass))); + .getAttribute("target-collection"), new ParserContextImpl().eval(targetClass))); } - if (getRequired(mappingElement, false)) { + if (getRequiredAttribute(mappingElement, false)) { mapper.addMapping(new RequiredMapping(source, target, parseTypeConverter(mappingElement))); } else { mapper.addMapping(new Mapping(source, target, parseTypeConverter(mappingElement))); @@ -1049,18 +807,9 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde } } - private boolean getRequired(Element element, boolean defaultValue) { - if (StringUtils.hasText(element.getAttribute(REQUIRED_ATTRIBUTE))) { - return ((Boolean) fromStringTo(Boolean.class).execute(element.getAttribute(REQUIRED_ATTRIBUTE))) - .booleanValue(); - } else { - return defaultValue; - } - } - private ConversionExecutor parseTypeConverter(Element element) { - String from = element.getAttribute(FROM_ATTRIBUTE); - String to = element.getAttribute(TO_ATTRIBUTE); + String from = element.getAttribute("from"); + String to = element.getAttribute("to"); if (StringUtils.hasText(from)) { if (StringUtils.hasText(to)) { ConversionService service = getLocalContext().getConversionService(); @@ -1076,6 +825,14 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde return null; } + private boolean getRequiredAttribute(Element element, boolean defaultValue) { + if (StringUtils.hasText(element.getAttribute("required"))) { + return ((Boolean) fromStringTo(Boolean.class).execute(element.getAttribute("required"))).booleanValue(); + } else { + return defaultValue; + } + } + private FlowExecutionExceptionHandler[] parseExceptionHandlers(Element element) { FlowExecutionExceptionHandler[] transitionExecutingHandlers = parseTransitionExecutingExceptionHandlers(element); FlowExecutionExceptionHandler[] customHandlers = parseCustomExceptionHandlers(element); @@ -1089,18 +846,18 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde private FlowExecutionExceptionHandler[] parseTransitionExecutingExceptionHandlers(Element element) { List transitionElements = Collections.EMPTY_LIST; - if (isFlowElement(element)) { - Element globalTransitionsElement = DomUtils.getChildElementByTagName(element, GLOBAL_TRANSITIONS_ELEMENT); + if (isRootFlowElement(element)) { + Element globalTransitionsElement = DomUtils.getChildElementByTagName(element, "global-transitions"); if (globalTransitionsElement != null) { - transitionElements = DomUtils.getChildElementsByTagName(globalTransitionsElement, TRANSITION_ELEMENT); + transitionElements = DomUtils.getChildElementsByTagName(globalTransitionsElement, "transition"); } } else { - transitionElements = DomUtils.getChildElementsByTagName(element, TRANSITION_ELEMENT); + transitionElements = DomUtils.getChildElementsByTagName(element, "transition"); } List exceptionHandlers = new LinkedList(); for (Iterator it = transitionElements.iterator(); it.hasNext();) { Element transitionElement = (Element) it.next(); - if (StringUtils.hasText(transitionElement.getAttribute(ON_EXCEPTION_ATTRIBUTE))) { + if (StringUtils.hasText(transitionElement.getAttribute("on-exception"))) { exceptionHandlers.add(parseTransitionExecutingExceptionHandler(transitionElement)); } } @@ -1110,17 +867,17 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde private FlowExecutionExceptionHandler parseTransitionExecutingExceptionHandler(Element element) { TransitionExecutingFlowExecutionExceptionHandler handler = new TransitionExecutingFlowExecutionExceptionHandler(); - Class exceptionClass = (Class) fromStringTo(Class.class).execute(element.getAttribute(ON_EXCEPTION_ATTRIBUTE)); + Class exceptionClass = (Class) fromStringTo(Class.class).execute(element.getAttribute("on-exception")); TargetStateResolver targetStateResolver = (TargetStateResolver) fromStringTo(TargetStateResolver.class) - .execute(element.getAttribute(TO_ATTRIBUTE)); + .execute(element.getAttribute("to")); handler.add(exceptionClass, targetStateResolver); - handler.getActionList().addAll(parseAnnotatedActions(element)); + handler.getActionList().addAll(parseActions(element)); return handler; } private FlowExecutionExceptionHandler[] parseCustomExceptionHandlers(Element element) { List exceptionHandlers = new LinkedList(); - List handlerElements = DomUtils.getChildElementsByTagName(element, EXCEPTION_HANDLER_ELEMENT); + List handlerElements = DomUtils.getChildElementsByTagName(element, "exception-handler"); for (int i = 0; i < handlerElements.size(); i++) { Element handlerElement = (Element) handlerElements.get(i); exceptionHandlers.add(parseCustomExceptionHandler(handlerElement)); @@ -1130,17 +887,17 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde } private FlowExecutionExceptionHandler parseCustomExceptionHandler(Element element) { - return (FlowExecutionExceptionHandler) getLocalContext().getBeanFactory().getBean( - element.getAttribute(BEAN_ATTRIBUTE), FlowExecutionExceptionHandler.class); + return (FlowExecutionExceptionHandler) getLocalContext().getBeanFactory().getBean(element.getAttribute("bean"), + FlowExecutionExceptionHandler.class); } private void parseAndSetSecuredAttribute(Element element, MutableAttributeMap attributes) { - Element secured = DomUtils.getChildElementByTagName(element, SECURED_ELEMENT); + Element secured = DomUtils.getChildElementByTagName(element, "secured"); if (secured != null) { SecurityRule rule = new SecurityRule(); rule.setRequiredAuthorities(SecurityRule.convertAuthoritiesFromCommaSeparatedString(secured - .getAttribute(AUTHORITIES_ATTRIBUTE))); - String comparisonType = secured.getAttribute(MATCH_ATTRIBUTE); + .getAttribute("authorities"))); + String comparisonType = secured.getAttribute("match"); if ("any".equals(comparisonType)) { rule.setComparisonType(SecurityRule.COMPARISON_ANY); } else if ("all".equals(comparisonType)) { @@ -1149,12 +906,20 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde // default to any rule.setComparisonType(SecurityRule.COMPARISON_ANY); } - attributes.put(SecurityRule.SECURITY_AUTHORITY_ATTRIBUTE_NAME, rule); + attributes.put("secured", rule); } } private ConversionExecutor fromStringTo(Class targetType) throws ConversionException { - return getLocalContext().getConversionService().getConversionExecutor(String.class, targetType); + return getConversionService().getConversionExecutor(String.class, targetType); + } + + private ExpressionParser getExpressionParser() { + return getLocalContext().getExpressionParser(); + } + + private ConversionService getConversionService() { + return getLocalContext().getConversionService(); } private static class FlowRelativeResourceLoader implements ResourceLoader { diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/spring-webflow-2.0.xsd b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/spring-webflow-2.0.xsd index a8dda316..01e40059 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/spring-webflow-2.0.xsd +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/spring-webflow-2.0.xsd @@ -77,15 +77,6 @@ Goes out of scope when this local flow session ends. - - - - - - - @@ -95,79 +86,25 @@ The default scope type. - + -The action referenced by this element must implement the org.springframework.webflow.execution.Action -interface. The action may be a MultiAction and if so the 'method' attribute can be used to -specify the target method to invoke. -
-An action may be annotated with attributes that can be used to affect the action's execution. -]]> -
-
-
- - - - -The bean to invoke is typically an arbitrary "POJO" (Plain Old Java Object) with no -dependency on Spring Web Flow. -
-Use this element when the logic to invoke is encapsulated within an object you define. -This element can be used to invoke *any* public method on any bean. -
-If the target method accepts arguments they may be specified in order by using the -'method-arguments' sub-element. -
-If the target method returns a value that value may be exposed to the flow using -the 'method-result' sub-element. -
-For example: -

-	<bean-action bean="orderClerk" method="placeOrder">
-		<method-arguments>
-			<argument expression="flowScope.order"/>
-		</method-arguments>
-		<method-result name="orderConfirmation"/>
-	</bean-action>
-
-The above example instructs this flow to invoke the "placeOrder" method on the "orderClerk" bean, -passing the value of "flowScope.order" as the method argument. After method invocation the -method return value is exposed in the default scope under the name "orderConfirmation". -]]> - - - - - - - -Use this element when the logic to invoke is encapsulated within an object inside -the flow request context. This element can be used to invoke *any* public method on -a flow-managed bean. -
-For example: -
-	<evaluate-action expression="flowScope.interview.nextQuestion()">
-	    <evaluation-result name="question"/>
-	</evaluate-action>
-
-The above example instructs this flow to invoke the "nextQuestion" method on the "interview" bean -in flow scope. After method invocation the method return value is exposed in the default -scope under the name "question". +Evaluates an arbitrary expression aganst the flow request context. ]]>
+ + + + + + + @@ -184,7 +121,7 @@ This action always returns a "success" event unless an exception is thrown. ]]> - + @@ -227,7 +164,7 @@ A flow may also exhibit the following characteristics: (See the <exception-handler/> element)
  • Import one or more local bean definition files defining custom flow artifacts -(such as actions, exception handlers, view selectors, transition criteria, etc). +(such as actions, exception handlers, view factories, transition criteria, etc). (See the <import/> element) @@ -288,14 +225,6 @@ that launched this flow.
  • The 'target' of each mapping is this flow execution's RequestContext, exposing access to data structures such as 'flowScope'. -
    -For example: -
    -    <input-mapper>
    -        <input-attribute name="id"/>
    -    </input-mapper>
    -
    -... maps the value of "id" input attribute to the "id" attribute in this flow's scope. ]]> @@ -440,14 +369,6 @@ internal data structures such as 'flowScope'.
  • The 'target' of each mapping is the flow output map that will contain the output returned to the caller that launched this flow. -
    -For example: -
    -    <output-mapper>
    -        <mapping source="flowScope.myFlowAttribute" target="clientOutputAttribute"/>
    -    </output-mapper>
    -
    -... maps the value of "myFlowAttribute" in flow scope to "clientOutputAttribute" in this flow's output map. ]]> @@ -646,7 +567,7 @@ fully-qualified class (e.g. 'java.lang.Integer'). The class cannot be abstract - + @@ -743,304 +664,8 @@ execution of this flow definition. Exception handlers may be attached at the sta - + - - - - - - - - - - - - - -If the referenced bean implements the org.springframework.webflow.execution.Action interface it is -retrieved from the factory and used as is. If the bean is not an Action an exception is thrown. -
    -This is similar to the <ref bean="myBean"/> notation of the Spring beans DTD. -]]> -
    -
    -
    - - - - -This can be used to execute actions in an ordered chain, where the flow responds -to the the last action result in the chain: -
    -    <action-state id="setupForm">
    -        <action name="setupForm" bean="formAction" method="setupForm"/>
    -        <action name="loadReferenceData" bean="formAction" method="loadReferenceData"/>
    -        <transition on="loadReferenceData.success" to="displayForm">
    -    </action-state>
    -
    -... will execute 'setupForm' followed by 'loadRefenceData', then transition the flow to -the 'displayForm' state on a successful 'loadReferenceData' invocation. -
    -An action with a name is often referred to as a "named action". -]]> -
    -
    -
    - - - - -Use this attribute when the action is a "multi action" extending -org.springframework.webflow.action.MultiAction. The value should be -name of the method to invoke on the multi-action instance. -The method's implementation must have the following signature: -
    -    public Event <methodName>(RequestContext context);
    -
    -As an example: -
    -	<action bean="formAction" method="setupForm"/>
    -
    - ... might invoke: -
    -	public class FormAction extends MultiAction {
    -		public Event setupForm(RequestContext context) {
    -			return success();
    -		}
    -	}
    -
    -]]> -
    -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -It is expected the referenced bean be a simple POJO that does not implement the Spring Web Flow -Action interface. The method to invoke, specified using the 'method' attribute, -will be adapted to the Action interface automatically. -
    -This is similar to the <ref bean="myBean"/> notation of the Spring beans DTD. -]]> -
    -
    -
    - - - - -This can be used to execute actions in an ordered chain, where the flow responds -to the the last action result in the chain: -
    -    <action-state id="setupForm">
    -        <action name="setupForm" bean="formAction" method="setupForm"/>
    -        <action name="loadReferenceData" bean="formAction" method="loadReferenceData"/>
    -        <transition on="loadReferenceData.success" to="displayForm">
    -    </action-state>
    -
    -... will execute 'setupForm' followed by 'loadRefenceData', then transition the flow to -the 'displayForm' state on a successful 'loadReferenceData' invocation. -
    -An action with a name is often referred to as a "named action". -]]> -
    -
    -
    - - - - -If the method has parameters the arguments to those parameters should be specified using -the 'method-arguments' element. -
    -If the method returns a value that should be exposed to this flow, the 'method-result' element -should be specified. -]]> -
    -
    -
    -
    -
    - - - - - - - - -Typically used to pass a value from a flow scope type into this bean method as an argument. -
    -Examples: -
    -	<argument expression="flowScope.order"/>
    -
    -... passes in the value of the 'order' attribute in flow scope. -
    -	<argument expression="'a constant'"/>
    -
    -... passes in the 'a constant' literal. -]]> -
    -
    -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  • request - The result goes out of scope when the call into this flow that invoked this method completes. -
  • flash - The result goes out of scope when the next user event is signaled. -
  • flow - The result goes out of scope when this local flow session ends. -
  • conversation - The result goes out of scope when the overall conversation governing this flow execution ends. - -
    -If not specified then the name attribute must be a fully resolvable expression such as "#{flowScope.myResult}". -]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1059,7 +684,7 @@ A fully resolvable expression such as #{flowScope.foo} or #{bean.bar} for exposi - + - - - - -This can be used to execute actions in an ordered chain, where the flow responds -to the the last action result in the chain: -
    -    <action-state id="setupForm">
    -        <evaluate-action name="firstInterviewQuestion" bean="flowScope.interview.firstQuestion()"/>
    -        <action name="setupForm" bean="formAction" method="setupForm"/>
    -        <transition on="setupForm.success" to="displayForm">
    -    </action-state>
    -
    -... will execute 'firstInterviewQuestion' followed by 'setupForm', then transition the flow to -the 'displayForm' state on a successful 'setupForm' invocation. -
    -An action with a name is often referred to as a "named action". -]]> -
    -
    -
    - - - - - and will -be removed in a future release. + + + + + + + - - - - - - - - - - - - - - -
  • request - The result goes out of scope when the call into this flow that evaluated this expression completes. -
  • flash - The result goes out of scope when the next user event is signaled. -
  • flow - The result goes out of scope when this local flow session ends. -
  • conversation - The result goes out of scope when the overall conversation governing this flow execution ends. - -
    -If not specified the name attribute must be a fully resolvable expression such as "#{flowScope.foo}". -]]> - - + + - - - - - - - - - + + - - - - - - - - - - + - - - - - - - -
  • request - The attribute goes out of scope when the call into this flow that sets the attribute completes. -
  • flash - The attribute goes out of scope when the next user event is signaled. -
  • flow - The attribute goes out of scope when this local flow session ends. -
  • conversation - The attribute goes out of scope when the overall conversation governing this flow execution ends. - -
    -If not specified the attribute to set must be a fully resolvable expression such as "#{flowScope.foo}". ]]> @@ -1195,32 +728,6 @@ If not specified the attribute to set must be a fully resolvable expression such - - - - - - - -This can be used to execute actions in an ordered chain, where the flow responds -to the the last action result in the chain: -
    -    <action-state id="processSubmit">
    -        <action name="processSubmit" bean="formAction" method="processSubmit"/>
    -        <set name="setFormSubmitted" attribute="formSubmitted" value="true"/>
    -        <transition on="setFormSubmitted.success" to="thankYou">
    -    </action-state>
    -
    -... will execute 'processSubmit' followed by 'setFormSubmitted', then transition the flow to -the 'thankYou' state on a successful 'setFormSubmitted' invocation. -
    -An action with a name is often referred to as a "named action". ]]>
    @@ -1638,32 +1145,38 @@ Defines state entry logic to be executed. This logic will always execute when th ]]> - - - - - -For the input mapper the following mapping characteristics apply: -
      -
    • The 'source' of each mapping is the RequestContext, exposing access to internal -data structures of this flow such as flowScope. -
    • The 'target' of each input mapping is the subflow's input Map. -
    -
    -For the output mapper the following mapping characteristics apply: -
      -
    • The 'source' of each output mapping is the subflow's output Map. -
    • The 'target' of each output mapping is the RequestContext, exposing access to -internal data structures of this flow such as flowScope. -
    -]]> -
    -
    -
    + + + + + +
  • The 'source' of each mapping is this flow execution's RequestContext, exposing access to +internal data structures such as 'flowScope'. +
  • The 'target' of each mapping is the input map that will be passed to the subflow. + +]]> + + + + + + + +For the output mapper the following mapping characteristics apply: +
      +
    • The 'source' of each mapping is a Map containing all the output returned by the subflow. +
    • The 'target' of each mapping is this flow execution's RequestContext, exposing access to +data structures such as 'flowScope'. +
    +]]> +
    +
    +
    @@ -1697,7 +1210,7 @@ execution of this flow definition. Exception handlers may be attached at the sta - + + + + + + + + - - - - - - - -
  • The 'source' of each mapping is the RequestContext, exposing access to internal -data structures of this flow such as flowScope. -
  • The 'target' of each input mapping is the subflow's input map. - -
    -For example: -
    -    <input-mapper>
    -	    <mapping source="flowScope.myFlowAttribute" target="subflowInputAttribute"/>
    -    </input-mapper>
    -
    -... maps the value of "flowScope.myFlowAttribute" to the "subflowInputAttribute" in the subflow input map. -]]> - - - - - - - -
  • The 'source' of each output mapping is the subflow's output map. -
  • The 'target' of each output mapping is the RequestContext, exposing access to -internal data structures of this flow such as flowScope. - -
    -For example: -
    -    <output-mapper>
    -	    <mapping source="aSubflowOutputAttribute" target="flowScope.myFlowAttribute"/>
    -    </output-mapper>
    -
    -... maps the value of "aSubflowOutputAttribute" in the subflow output map to "myFlowAttribute" -in flow scope. -]]> - - - - - - - - -Use this as an alternative to the child input-mapper and output-mapper elements -when you need full control of attribute mapping behavior for this subflow state. -]]> - - - - - - @@ -1825,14 +1280,6 @@ internal data structures such as 'flowScope'.
  • The 'target' of each mapping is the flow output map that will contain the output returned to the caller that launched this flow. -
    -For example: -
    -    <output-mapper>
    -        <mapping source="flowScope.myFlowAttribute" target="clientOutputAttribute"/>
    -    </output-mapper>
    -
    -... maps the value of "myFlowAttribute" in flow scope to "clientOutputAttribute" in this flow's output map. ]]> @@ -1891,13 +1338,23 @@ context. The exact semantics regarding the interpretation of this value are determined by the installed TextToViewSelector converter.
    -Note when no view name is provided, this view state will make a "null" view selection. A null -view does not request the rendering of a view, it only pauses the flow and returns control -the client. Use a null view when another state is expected to generate the response. +Note when no view name is provided, this end state will issue no final response. In this case, +it is expected the calling flow controller will handle issuing the final response. ]]> - + + + + + + + + diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/support/AbstractFlowAttributeMapper.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/support/AbstractSubflowAttributeMapper.java similarity index 69% rename from spring-webflow/src/main/java/org/springframework/webflow/engine/support/AbstractFlowAttributeMapper.java rename to spring-webflow/src/main/java/org/springframework/webflow/engine/support/AbstractSubflowAttributeMapper.java index 926f41b0..c2f9b2ae 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/support/AbstractFlowAttributeMapper.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/support/AbstractSubflowAttributeMapper.java @@ -22,50 +22,49 @@ import org.springframework.binding.mapping.MappingContext; import org.springframework.webflow.core.collection.AttributeMap; import org.springframework.webflow.core.collection.LocalAttributeMap; import org.springframework.webflow.core.collection.MutableAttributeMap; -import org.springframework.webflow.engine.FlowAttributeMapper; +import org.springframework.webflow.engine.SubflowAttributeMapper; import org.springframework.webflow.execution.RequestContext; /** - * Convenient base class for attribute mapper implementations. Encapsulates common attribute mapper workflow. Contains - * no state. Subclasses must override the {@link #getInputMapper()} and {@link #getOutputMapper()} methods to return the - * input mapper and output mapper, respectively. + * Convenient base class for subflow attribute mapper implementations. Encapsulates common attribute mapper workflow. + * Contains no state. Subclasses must override the {@link #getInputMapper()} and {@link #getOutputMapper()} methods to + * return the input mapper and output mapper, respectively. * * @author Keith Donald */ -public abstract class AbstractFlowAttributeMapper implements FlowAttributeMapper, Serializable { +public abstract class AbstractSubflowAttributeMapper implements SubflowAttributeMapper, Serializable { /** * Returns the input mapper to use to map attributes of a parent flow {@link RequestContext} to a subflow input - * attribute {@link AttributeMap map}. + * {@link AttributeMap attribute map}. * @return the input mapper, or null if none * @see #createFlowInput(RequestContext) */ protected abstract AttributeMapper getInputMapper(); /** - * Returns the output mapper to use to map attributes from a subflow output attribute map to the - * {@link RequestContext}. + * Returns the output mapper to use to map attributes from a subflow output {@link AttributeMap attribute map} to + * the {@link RequestContext}. * @return the output mapper, or null if none * @see #mapFlowOutput(AttributeMap, RequestContext) */ protected abstract AttributeMapper getOutputMapper(); public MutableAttributeMap createFlowInput(RequestContext context) { - if (getInputMapper() != null) { + AttributeMapper inputMapper = getInputMapper(); + if (inputMapper != null) { LocalAttributeMap input = new LocalAttributeMap(); - // map from request context to input map - getInputMapper().map(context, input, getMappingContext(context)); + inputMapper.map(context, input, getMappingContext(context)); return input; } else { - // an empty, but modifiable map return new LocalAttributeMap(); } } public void mapFlowOutput(AttributeMap subflowOutput, RequestContext context) { + AttributeMapper outputMapper = getOutputMapper(); if (getOutputMapper() != null && subflowOutput != null) { - // map from subflow output map to request context - getOutputMapper().map(subflowOutput, context, getMappingContext(context)); + outputMapper.map(subflowOutput, context, getMappingContext(context)); } } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/ImmutableFlowAttributeMapper.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/support/GenericSubflowAttributeMapper.java similarity index 73% rename from spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/ImmutableFlowAttributeMapper.java rename to spring-webflow/src/main/java/org/springframework/webflow/engine/support/GenericSubflowAttributeMapper.java index 5cab6ed5..589b72cd 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/ImmutableFlowAttributeMapper.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/support/GenericSubflowAttributeMapper.java @@ -13,23 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.webflow.engine.builder.xml; +package org.springframework.webflow.engine.support; import java.io.Serializable; import org.springframework.binding.mapping.AttributeMapper; import org.springframework.core.style.ToStringCreator; -import org.springframework.webflow.engine.support.AbstractFlowAttributeMapper; /** - * Simple flow attribute mapper that holds an input and output mapper strategy. This is an internal helper class of the - * XmlFlowBuilder. - * - * @see org.springframework.webflow.engine.builder.xml.XmlFlowBuilder + * Simple flow attribute mapper that holds an input and output mapper strategy. * * @author Keith Donald */ -final class ImmutableFlowAttributeMapper extends AbstractFlowAttributeMapper implements Serializable { +public final class GenericSubflowAttributeMapper extends AbstractSubflowAttributeMapper implements Serializable { private final AttributeMapper inputMapper; @@ -40,7 +36,7 @@ final class ImmutableFlowAttributeMapper extends AbstractFlowAttributeMapper imp * @param inputMapper the input mapping strategy * @param outputMapper the output mapping strategy */ - public ImmutableFlowAttributeMapper(AttributeMapper inputMapper, AttributeMapper outputMapper) { + public GenericSubflowAttributeMapper(AttributeMapper inputMapper, AttributeMapper outputMapper) { this.inputMapper = inputMapper; this.outputMapper = outputMapper; } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/support/TransitionCriteriaChain.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/support/TransitionCriteriaChain.java index c7beadbe..2b25c2ca 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/support/TransitionCriteriaChain.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/support/TransitionCriteriaChain.java @@ -21,9 +21,9 @@ import java.util.LinkedList; import java.util.List; import org.springframework.core.style.ToStringCreator; -import org.springframework.webflow.engine.AnnotatedAction; import org.springframework.webflow.engine.TransitionCriteria; import org.springframework.webflow.engine.WildcardTransitionCriteria; +import org.springframework.webflow.execution.Action; import org.springframework.webflow.execution.RequestContext; /** @@ -85,7 +85,7 @@ public class TransitionCriteriaChain implements TransitionCriteria { * Create a transition criteria chain chaining given list of actions. * @param actions the actions (and their execution properties) to chain together */ - public static TransitionCriteria criteriaChainFor(AnnotatedAction[] actions) { + public static TransitionCriteria criteriaChainFor(Action[] actions) { if (actions == null || actions.length == 0) { return WildcardTransitionCriteria.INSTANCE; } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/View.java b/spring-webflow/src/main/java/org/springframework/webflow/execution/View.java index de067cf8..52d471b7 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/View.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/View.java @@ -24,6 +24,11 @@ package org.springframework.webflow.execution; */ public interface View { + /** + * Well-known attribute name for storing a render fragments value. + */ + public static final String RENDER_FRAGMENTS_ATTRIBUTE = "webflow-render-fragments"; + /** * Render this view's content. */ diff --git a/spring-webflow/src/test/java/log4j.xml b/spring-webflow/src/test/java/log4j.xml new file mode 100644 index 00000000..b50d654b --- /dev/null +++ b/spring-webflow/src/test/java/log4j.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/action/ActionResultExposerTests.java b/spring-webflow/src/test/java/org/springframework/webflow/action/ActionResultExposerTests.java index 25e4ab99..6d568aee 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/action/ActionResultExposerTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/action/ActionResultExposerTests.java @@ -2,160 +2,37 @@ package org.springframework.webflow.action; import junit.framework.TestCase; -import org.jboss.el.ExpressionFactoryImpl; -import org.springframework.binding.expression.Expression; -import org.springframework.binding.expression.ExpressionParser; -import org.springframework.binding.expression.support.ParserContextImpl; -import org.springframework.webflow.core.collection.MutableAttributeMap; -import org.springframework.webflow.core.expression.WebFlowOgnlExpressionParser; -import org.springframework.webflow.core.expression.el.WebFlowELExpressionParser; -import org.springframework.webflow.execution.RequestContext; -import org.springframework.webflow.execution.RequestContextHolder; -import org.springframework.webflow.execution.ScopeType; +import org.springframework.binding.convert.support.DefaultConversionService; +import org.springframework.binding.expression.support.StaticExpression; import org.springframework.webflow.test.MockRequestContext; public class ActionResultExposerTests extends TestCase { - public void testExposeResult_ScopeSpecified() { - - String valueToSet = "myValue"; - - ExpressionParser parser = new WebFlowELExpressionParser(new ExpressionFactoryImpl()); - Expression nameExpression = parser.parseExpression("#{foo}", new ParserContextImpl() - .eval(MutableAttributeMap.class)); - - ActionResultExposer exposer = new ActionResultExposer(nameExpression, ScopeType.REQUEST, null); - - 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", valueToSet, context.getRequestScope().get("foo")); + public void testEvaluateExpressionNullResult() throws Exception { + StaticExpression resultExpression = new StaticExpression(""); + ActionResultExposer exposer = new ActionResultExposer(resultExpression, null, null); + MockRequestContext context = new MockRequestContext(); + exposer.exposeResult("foo", context); + assertEquals("foo", resultExpression.getValue(null)); } - 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 testEvaluateExpressionResultExposerWithTypeConversion() throws Exception { + StaticExpression resultExpression = new StaticExpression(""); + ActionResultExposer exposer = new ActionResultExposer(resultExpression, Integer.class, + new DefaultConversionService()); + MockRequestContext context = new MockRequestContext(); + exposer.exposeResult("3", context); + assertEquals(new Integer(3), resultExpression.getValue(null)); } - public void testExposeResult_ScopeExpression() { + public void testEvaluateExpressionResultExposerWithTypeConversionForgotArgument() throws Exception { + StaticExpression resultExpression = new StaticExpression(""); + try { + new ActionResultExposer(resultExpression, Integer.class, null); + fail("should have failed iae"); + } catch (IllegalArgumentException e) { - String valueToSet = "myValue"; - - ExpressionParser parser = new WebFlowELExpressionParser(new ExpressionFactoryImpl()); - Expression nameExpression = parser.parseExpression("#{requestScope.foo}", new ParserContextImpl() - .eval(RequestContext.class)); - - ActionResultExposer exposer = new ActionResultExposer(nameExpression, null, null); - - RequestContext context = new MockRequestContext(); - RequestContextHolder.setRequestContext(context); - - exposer.exposeResult(valueToSet, context); - - assertTrue("Key 'foo' not found in request scope", context.getRequestScope().contains("foo")); - 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(); - RequestContextHolder.setRequestContext(context); - - 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"; - - ExpressionParser parser = new WebFlowELExpressionParser(new ExpressionFactoryImpl()); - Expression nameExpression = parser.parseExpression("#{bean.foo}", new ParserContextImpl() - .eval(RequestContext.class)); - - ActionResultExposer exposer = new ActionResultExposer(nameExpression, null, null); - - RequestContext context = new MockRequestContext(); - RequestContextHolder.setRequestContext(context); - TestBean bean = new TestBean(); - context.getRequestScope().put("bean", bean); - - exposer.exposeResult(valueToSet, context); - - assertEquals("Value of foo is incorrect", valueToSet, bean.getFoo()); - } - - public void testExposeResult_OGNL_ScopeSpecified() { - - String valueToSet = "myValue"; - - ExpressionParser parser = new WebFlowOgnlExpressionParser(); - Expression nameExpression = parser.parseExpression("${foo}", new ParserContextImpl() - .eval(MutableAttributeMap.class)); - - ActionResultExposer exposer = new ActionResultExposer(nameExpression, ScopeType.REQUEST, null); - - 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", valueToSet, context.getRequestScope().get("foo")); - } - - public void testExposeResult_OGNL_ScopeExpression() { - - String valueToSet = "myValue"; - - ExpressionParser parser = new WebFlowOgnlExpressionParser(); - Expression nameExpression = parser.parseExpression("${requestScope.foo}", new ParserContextImpl() - .eval(RequestContext.class)); - - ActionResultExposer exposer = new ActionResultExposer(nameExpression, null, null); - - 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", valueToSet, context.getRequestScope().get("foo")); - } - - public class TestBean { - - private String foo; - - public String getFoo() { - return foo; - } - - public void setFoo(String foo) { - this.foo = foo; } } + } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/action/EvaluateActionTests.java b/spring-webflow/src/test/java/org/springframework/webflow/action/EvaluateActionTests.java index 4f5e249a..f4e7df55 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/action/EvaluateActionTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/action/EvaluateActionTests.java @@ -17,116 +17,30 @@ package org.springframework.webflow.action; import junit.framework.TestCase; -import org.jboss.el.ExpressionFactoryImpl; -import org.springframework.binding.expression.Expression; -import org.springframework.binding.expression.ExpressionParser; -import org.springframework.binding.expression.support.ParserContextImpl; import org.springframework.binding.expression.support.StaticExpression; -import org.springframework.webflow.core.collection.MutableAttributeMap; -import org.springframework.webflow.core.expression.el.WebFlowELExpressionParser; import org.springframework.webflow.execution.Event; -import org.springframework.webflow.execution.RequestContext; -import org.springframework.webflow.execution.RequestContextHolder; -import org.springframework.webflow.execution.ScopeType; import org.springframework.webflow.test.MockRequestContext; /** * Unit tests for {@link EvaluateAction}. - * * @author Jeremy Grelle */ public class EvaluateActionTests extends TestCase { - private MockRequestContext context = new MockRequestContext(); - - protected void setUp() throws Exception { - RequestContextHolder.setRequestContext(context); - context.getFlowScope().put("foo", "bar"); - context.getFlowScope().put("bean", new TestBean()); - } - - public void testEvaluateExpressionNoResult() throws Exception { - EvaluateAction action = new EvaluateAction(new StaticExpression("bar")); + public void testEvaluateExpressionNoResultExposer() throws Exception { + EvaluateAction action = new EvaluateAction(new StaticExpression("bar"), null); + MockRequestContext context = new MockRequestContext(); Event result = action.execute(context); assertEquals("bar", result.getId()); - assertNull(context.getFlowScope().get("baz")); } - public void testEvaluateExpressionResult_ScopeSpecfied() throws Exception { - ExpressionParser parser = new WebFlowELExpressionParser(new ExpressionFactoryImpl()); - Expression nameExpression = parser.parseExpression("#{baz}", new ParserContextImpl() - .eval(MutableAttributeMap.class)); - - EvaluateAction action = new EvaluateAction(new StaticExpression("bar"), new ActionResultExposer(nameExpression, - ScopeType.FLOW, null)); + public void testEvaluateExpressionResultExposer() throws Exception { + StaticExpression resultExpression = new StaticExpression(""); + EvaluateAction action = new EvaluateAction(new StaticExpression("bar"), new ActionResultExposer( + resultExpression, null, null)); + MockRequestContext context = new MockRequestContext(); Event result = action.execute(context); assertEquals("bar", result.getId()); - assertEquals("bar", context.getFlowScope().get("baz")); + assertEquals("bar", resultExpression.getValue(null)); } - - public void testBeanResult_ScopeSpecified() throws Exception { - ExpressionParser parser = new WebFlowELExpressionParser(new ExpressionFactoryImpl()); - Expression nameExpression = parser.parseExpression("#{baz}", new ParserContextImpl() - .eval(MutableAttributeMap.class)); - - TestBean bean = new TestBean(); - - EvaluateAction action = new EvaluateAction(new StaticExpression(bean), new ActionResultExposer(nameExpression, - 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)); - Event result = action.execute(context); - assertEquals("bar", result.getId()); - assertEquals("bar", context.getFlowScope().get("baz")); - } - - public void testEvaluateExpressionResult_ScopeSearch() throws Exception { - ExpressionParser parser = new WebFlowELExpressionParser(new ExpressionFactoryImpl()); - Expression nameExpression = parser.parseExpression("#{baz.foo}", new ParserContextImpl() - .eval(RequestContext.class)); - - TestBean bean = new TestBean(); - context.getFlowScope().put("baz", bean); - - EvaluateAction action = new EvaluateAction(new StaticExpression("bar"), new ActionResultExposer(nameExpression, - null, null)); - Event result = action.execute(context); - assertEquals("bar", result.getId()); - assertEquals("bar", bean.getFoo()); - } - - public class TestBean { - - private String foo; - - public String getFoo() { - return foo; - } - - public void setFoo(String foo) { - this.foo = foo; - } - } -} +} \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/action/RenderActionTests.java b/spring-webflow/src/test/java/org/springframework/webflow/action/RenderActionTests.java new file mode 100644 index 00000000..bf023102 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/action/RenderActionTests.java @@ -0,0 +1,43 @@ +package org.springframework.webflow.action; + +import junit.framework.TestCase; + +import org.springframework.binding.expression.Expression; +import org.springframework.binding.expression.support.StaticExpression; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.View; +import org.springframework.webflow.test.MockRequestContext; + +public class RenderActionTests extends TestCase { + public void testRenderAction() throws Exception { + StaticExpression name = new StaticExpression("frag1"); + StaticExpression name2 = new StaticExpression("frag2"); + RenderAction action = new RenderAction(new Expression[] { name, name2 }); + MockRequestContext context = new MockRequestContext(); + Event result = action.execute(context); + assertEquals("success", result.getId()); + String[] fragments = (String[]) context.getFlashScope().getArray(View.RENDER_FRAGMENTS_ATTRIBUTE, + String[].class); + assertEquals("frag1", fragments[0]); + assertEquals("frag2", fragments[1]); + } + + public void testIllegalNullArg() { + try { + new RenderAction(null); + fail("iae"); + } catch (IllegalArgumentException e) { + + } + } + + public void testIllegalEmptyArg() { + try { + new RenderAction(new Expression[0]); + fail("iae"); + } catch (IllegalArgumentException e) { + + } + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/action/SetActionTests.java b/spring-webflow/src/test/java/org/springframework/webflow/action/SetActionTests.java index 6fee07dd..c95bc6d6 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/action/SetActionTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/action/SetActionTests.java @@ -2,182 +2,17 @@ package org.springframework.webflow.action; import junit.framework.TestCase; -import org.jboss.el.ExpressionFactoryImpl; -import org.springframework.binding.expression.Expression; -import org.springframework.binding.expression.ExpressionParser; -import org.springframework.binding.expression.ognl.OgnlExpressionParser; -import org.springframework.binding.expression.support.ParserContextImpl; -import org.springframework.webflow.core.collection.MutableAttributeMap; -import org.springframework.webflow.core.expression.WebFlowOgnlExpressionParser; -import org.springframework.webflow.core.expression.el.WebFlowELExpressionParser; -import org.springframework.webflow.execution.RequestContext; -import org.springframework.webflow.execution.RequestContextHolder; -import org.springframework.webflow.execution.ScopeType; +import org.springframework.binding.expression.support.StaticExpression; +import org.springframework.webflow.execution.Event; import org.springframework.webflow.test.MockRequestContext; public class SetActionTests extends TestCase { - - public void testExecute_AttrExpression_ScopeSpecified() throws Exception { - - String valueToSet = "myValue"; - - ExpressionParser parser = new WebFlowELExpressionParser(new ExpressionFactoryImpl()); - - Expression attributeExpression = parser.parseExpression("#{foo}", new ParserContextImpl() - .eval(MutableAttributeMap.class)); - ScopeType scope = ScopeType.REQUEST; - Expression valueExpression = parser.parseExpression(valueToSet, new ParserContextImpl() - .eval(RequestContext.class)); - - SetAction action = new SetAction(attributeExpression, scope, valueExpression); - - RequestContext context = new MockRequestContext(); - action.execute(context); - - assertTrue(context.getRequestScope().contains("foo")); - assertEquals(valueToSet, context.getRequestScope().get("foo")); - } - - public void testExecute_ScopeExpression() throws Exception { - - String valueToSet = "myValue"; - - ExpressionParser parser = new WebFlowELExpressionParser(new ExpressionFactoryImpl()); - - Expression attributeExpression = parser.parseExpression("#{requestScope.foo}", new ParserContextImpl() - .eval(RequestContext.class)); - ScopeType scope = null; - Expression valueExpression = parser.parseExpression(valueToSet, new ParserContextImpl() - .eval(RequestContext.class)); - - SetAction action = new SetAction(attributeExpression, scope, valueExpression); - - RequestContext context = new MockRequestContext(); - RequestContextHolder.setRequestContext(context); - action.execute(context); - - assertTrue(context.getRequestScope().contains("foo")); - assertEquals(valueToSet, context.getRequestScope().get("foo")); - } - - public void testExecute_SearchExpression() throws Exception { - - String valueToSet = "myValue"; - - ExpressionParser parser = new WebFlowELExpressionParser(new ExpressionFactoryImpl()); - - Expression attributeExpression = parser.parseExpression("#{bean.foo}", new ParserContextImpl() - .eval(RequestContext.class)); - ScopeType scope = null; - Expression valueExpression = parser.parseExpression(valueToSet, new ParserContextImpl() - .eval(RequestContext.class)); - - SetAction action = new SetAction(attributeExpression, scope, valueExpression); - - RequestContext context = new MockRequestContext(); - RequestContextHolder.setRequestContext(context); - TestBean bean = new TestBean(); - context.getRequestScope().put("bean", bean); - - action.execute(context); - - assertEquals(valueToSet, bean.getFoo()); - } - - public void testExecute_OGNL_ScopeSpecified() throws Exception { - - String valueToSet = "myValue"; - - OgnlExpressionParser parser = new WebFlowOgnlExpressionParser(); - - Expression attributeExpression = parser.parseExpression("${foo}", new ParserContextImpl() - .eval(MutableAttributeMap.class)); - ScopeType scope = ScopeType.REQUEST; - Expression valueExpression = parser.parseExpression(valueToSet, new ParserContextImpl() - .eval(RequestContext.class)); - - SetAction action = new SetAction(attributeExpression, scope, valueExpression); - - RequestContext context = new MockRequestContext(); - action.execute(context); - - assertTrue(context.getRequestScope().contains("foo")); - assertEquals(valueToSet, context.getRequestScope().get("foo")); - } - - public void testExecute_OGNL_ScopeExpression() throws Exception { - - String valueToSet = "myValue"; - - OgnlExpressionParser parser = new WebFlowOgnlExpressionParser(); - - Expression attributeExpression = parser.parseExpression("${requestScope.foo}", new ParserContextImpl() - .eval(RequestContext.class)); - Expression valueExpression = parser.parseExpression(valueToSet, new ParserContextImpl() - .eval(RequestContext.class)); - - SetAction action = new SetAction(attributeExpression, null, valueExpression); - - RequestContext context = new MockRequestContext(); - action.execute(context); - - assertTrue(context.getRequestScope().contains("foo")); - assertEquals(valueToSet, context.getRequestScope().get("foo")); - } - - public void testExecute_LegacyOGNL_ScopeSpecified() throws Exception { - - String valueToSet = "myValue"; - - OgnlExpressionParser parser = new WebFlowOgnlExpressionParser(); - parser.setAllowUndelimitedEvalExpressions(true); - - Expression attributeExpression = parser.parseExpression("foo", new ParserContextImpl() - .eval(MutableAttributeMap.class)); - ScopeType scope = ScopeType.REQUEST; - Expression valueExpression = parser.parseExpression("'" + valueToSet + "'", new ParserContextImpl() - .eval(RequestContext.class)); - - SetAction action = new SetAction(attributeExpression, scope, valueExpression); - - RequestContext context = new MockRequestContext(); - action.execute(context); - - assertTrue(context.getRequestScope().contains("foo")); - assertEquals(valueToSet, context.getRequestScope().get("foo")); - } - - public void testExecute_LegacyOGNL_ScopeExpression() throws Exception { - - String valueToSet = "myValue"; - - OgnlExpressionParser parser = new WebFlowOgnlExpressionParser(); - parser.setAllowUndelimitedEvalExpressions(true); - - Expression attributeExpression = parser.parseExpression("requestScope.foo", new ParserContextImpl() - .eval(RequestContext.class)); - Expression valueExpression = parser.parseExpression("'" + valueToSet + "'", new ParserContextImpl() - .eval(RequestContext.class)); - - SetAction action = new SetAction(attributeExpression, null, valueExpression); - - RequestContext context = new MockRequestContext(); - action.execute(context); - - assertTrue(context.getRequestScope().contains("foo")); - assertEquals(valueToSet, context.getRequestScope().get("foo")); - } - - public class TestBean { - - private String foo; - - public String getFoo() { - return foo; - } - - public void setFoo(String foo) { - this.foo = foo; - } + public void testSetAction() throws Exception { + StaticExpression name = new StaticExpression(""); + SetAction action = new SetAction(name, new StaticExpression("bar")); + MockRequestContext context = new MockRequestContext(); + Event result = action.execute(context); + assertEquals("success", result.getId()); + assertEquals("bar", name.getValue(null)); } } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/EndStateTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/EndStateTests.java index 99f7e43b..c3a0dab7 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/EndStateTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/EndStateTests.java @@ -17,6 +17,8 @@ package org.springframework.webflow.engine; import junit.framework.TestCase; +import org.springframework.binding.expression.EvaluationException; +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; @@ -74,12 +76,16 @@ public class EndStateTests extends TestCase { } public void testEnterEndStateTerminateFlowSession() { - Flow subflow = new Flow("mySubflow"); + final Flow subflow = new Flow("mySubflow"); EndState state = new EndState(subflow, "end"); MockFlowSession session = new MockFlowSession(subflow); Flow parent = new Flow("parent"); - SubflowState subflowState = new SubflowState(parent, "subflow", subflow); + SubflowState subflowState = new SubflowState(parent, "subflow", new AbstractGetValueExpression() { + public Object getValue(Object context) throws EvaluationException { + return subflow; + } + }); subflowState.getTransitionSet().add(new Transition(on("end"), to("end"))); new EndState(parent, "end"); diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/SubflowStateTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/SubflowStateTests.java index 2dde7187..1341f783 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/SubflowStateTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/SubflowStateTests.java @@ -17,6 +17,8 @@ package org.springframework.webflow.engine; 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.webflow.core.collection.AttributeMap; @@ -43,7 +45,11 @@ public class SubflowStateTests extends TestCase { public void setUp() { parentFlow = new Flow("parent"); subflow = new Flow("child"); - subflowState = new SubflowState(parentFlow, "subflow", subflow); + subflowState = new SubflowState(parentFlow, "subflow", new AbstractGetValueExpression() { + public Object getValue(Object context) throws EvaluationException { + return subflow; + } + }); context = new MockRequestControlContext(parentFlow); context.setCurrentState(subflowState); } @@ -58,7 +64,7 @@ public class SubflowStateTests extends TestCase { } public void testEnterWithInput() { - subflowState.setAttributeMapper(new FlowAttributeMapper() { + subflowState.setAttributeMapper(new SubflowAttributeMapper() { public MutableAttributeMap createFlowInput(RequestContext context) { return new LocalAttributeMap("foo", "bar"); } @@ -81,7 +87,7 @@ public class SubflowStateTests extends TestCase { } public void testReturnWithOutput() { - subflowState.setAttributeMapper(new FlowAttributeMapper() { + subflowState.setAttributeMapper(new SubflowAttributeMapper() { public MutableAttributeMap createFlowInput(RequestContext context) { return new LocalAttributeMap(); } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/TestAttributeMapper.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/TestSubflowAttributeMapper.java similarity index 95% rename from spring-webflow/src/test/java/org/springframework/webflow/engine/TestAttributeMapper.java rename to spring-webflow/src/test/java/org/springframework/webflow/engine/TestSubflowAttributeMapper.java index 617235e0..51bca8e0 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/TestAttributeMapper.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/TestSubflowAttributeMapper.java @@ -20,7 +20,7 @@ import org.springframework.webflow.core.collection.LocalAttributeMap; import org.springframework.webflow.core.collection.MutableAttributeMap; import org.springframework.webflow.execution.RequestContext; -class TestAttributeMapper implements FlowAttributeMapper { +class TestSubflowAttributeMapper implements SubflowAttributeMapper { public MutableAttributeMap createFlowInput(RequestContext context) { LocalAttributeMap inputMap = new LocalAttributeMap(); inputMap.put("childInputAttribute", context.getFlowScope().get("parentInputAttribute")); diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-action-evaluate-action.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-action-evaluate-action.xml new file mode 100644 index 00000000..ba71b418 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-action-evaluate-action.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-action-evaluate-bean.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-action-evaluate-bean.xml new file mode 100644 index 00000000..c85d1dae --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-action-evaluate-bean.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/test/search-flow.xml b/spring-webflow/src/test/java/org/springframework/webflow/test/search-flow.xml index 841b1841..edbedf2f 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/test/search-flow.xml +++ b/spring-webflow/src/test/java/org/springframework/webflow/test/search-flow.xml @@ -4,33 +4,26 @@ xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd"> - - + + - + - - - - - - - + + - - - - - - + + + +