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
This commit is contained in:
Keith Donald
2008-03-03 23:33:58 +00:00
parent 867485ad4c
commit 0a45b2c15d
29 changed files with 681 additions and 1767 deletions

View File

@@ -10,7 +10,9 @@
<classpathentry kind="lib" path="lib/buildtime/cglib.jar"/>
<classpathentry kind="lib" path="lib/test/clover.jar"/>
<classpathentry kind="lib" path="lib/buildtime/commons-beanutils.jar"/>
<classpathentry kind="lib" path="lib/test/commons-collections.jar"/>
<classpathentry kind="lib" path="lib/buildtime/commons-digester.jar"/>
<classpathentry kind="lib" path="lib/test/commons-lang.jar"/>
<classpathentry kind="lib" path="lib/buildtime/commons-fileupload.jar"/>
<classpathentry kind="lib" path="lib/buildtime/commons-logging.jar"/>
<classpathentry kind="lib" path="lib/buildtime/commons-validator.jar"/>
@@ -21,12 +23,16 @@
<classpathentry kind="lib" path="lib/buildtime/el-api.jar"/>
<classpathentry kind="lib" path="lib/buildtime/hibernate.jar"/>
<classpathentry kind="lib" path="lib/test/hsqldb.jar"/>
<classpathentry kind="lib" path="lib/test/log4j.jar"/>
<classpathentry kind="lib" path="lib/test/jboss-el.jar"/>
<classpathentry kind="lib" path="lib/buildtime/jta.jar"/>
<classpathentry kind="lib" path="lib/buildtime/junit.jar"/>
<classpathentry kind="lib" path="lib/global/ognl.jar"/>
<classpathentry kind="lib" path="lib/test/openjpa.jar"/>
<classpathentry kind="lib" path="lib/buildtime/oro.jar"/>
<classpathentry kind="lib" path="lib/buildtime/persistence-api.jar"/>
<classpathentry kind="lib" path="lib/buildtime/portlet-api.jar"/>
<classpathentry kind="lib" path="lib/test/serp.jar"/>
<classpathentry kind="lib" path="lib/buildtime/servlet-api.jar"/>
<classpathentry kind="lib" path="lib/test/spring-aop.jar"/>
<classpathentry kind="lib" path="lib/global/spring-beans.jar"/>
@@ -35,6 +41,7 @@
<classpathentry kind="lib" path="lib/global/spring-core.jar"/>
<classpathentry kind="lib" path="lib/test/spring-jdbc.jar"/>
<classpathentry kind="lib" path="lib/buildtime/spring-orm.jar"/>
<classpathentry kind="lib" path="lib/buildtime/spring-security-core.jar"/>
<classpathentry kind="lib" path="lib/test/spring-test.jar"/>
<classpathentry kind="lib" path="lib/buildtime/spring-tx.jar"/>
<classpathentry kind="lib" path="lib/global/spring-web.jar"/>
@@ -42,11 +49,5 @@
<classpathentry kind="lib" path="lib/buildtime/spring-webmvc-portlet.jar"/>
<classpathentry kind="lib" path="lib/buildtime/struts.jar"/>
<classpathentry kind="lib" path="lib/buildtime/xalan.jar"/>
<classpathentry kind="lib" path="lib/test/openjpa.jar"/>
<classpathentry kind="lib" path="lib/test/commons-lang.jar"/>
<classpathentry kind="lib" path="lib/test/serp.jar"/>
<classpathentry kind="lib" path="lib/test/commons-collections.jar"/>
<classpathentry kind="lib" path="lib/test/jboss-el.jar"/>
<classpathentry kind="lib" path="lib/buildtime/spring-security-core.jar"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

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

View File

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

View File

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

View File

@@ -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 <b>unrecoverable</b> exception occured, either checked or unchecked
* @throws Exception an <b>unrecoverable</b> exception occurred, either checked or unchecked
*/
public Event bindAndValidate(RequestContext context) throws Exception {
if (logger.isDebugEnabled()) {

View File

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

View File

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

View File

@@ -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.
* <p>
* An attribute mapper may map attributes of a parent flow down to a child flow as <i>input</i> 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
* <i>output</i> when the child session ends and control is returned to the parent flow.
* <p>
* For example, say you have the following parent flow session:
* <p>
*
* <pre>
* Parent Flow Session
* -------------------
* -&gt; flow = myFlow
* -&gt; flowScope = [map-&gt; attribute1=value1, attribute2=value2, attribute3=value3]
* </pre>
*
* <p>
* 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
* <i>which</i> attributes should be mapped, and <i>how</i> they will be mapped (for example, will the same attribute
* names be used between flows or not?).
* <p>
* For example:
* <p>
*
* <pre>
* Flow Attribute Mapper Configuration
* -----------------------------------
* -&gt; inputMappings = [map-&gt; flowScope.attribute1-&gt;attribute1, flowScope.attribute3-&gt;attribute4]
* -&gt; outputMappings = [map-&gt; attribute4-&gt;flowScope.attribute3]
* </pre>
*
* <p>
* The above example "Flow Attribute Mapper" specifies <code>inputMappings</code> 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.
* <p>
* Likewise, when a child flow ends the <code>outputMappings</code> 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.
* <p>
* 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.
* <p>
* 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 <i>input</i> to a spawning flow.
* <p>
* 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 <i>output</i> 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);
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -77,15 +77,6 @@ Goes out of scope when this local flow session ends.
<xsd:documentation>
<![CDATA[
Goes out of scope when the overall governing flow execution ends.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="default">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The default scope type.
]]>
</xsd:documentation>
</xsd:annotation>
@@ -95,79 +86,25 @@ The default scope type.
<xsd:group name="actionTypes">
<xsd:choice>
<xsd:element ref="action">
<xsd:element ref="evaluate">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Defines a single action to be executed. An action is a Web Flow specific command object
that executes arbitrary behavior.
<br>
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.
<br>
An action may be annotated with attributes that can be used to affect the action's execution.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="bean-action">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Defines a single method on a bean to execute as an action.
<br>
The bean to invoke is typically an arbitrary "POJO" (Plain Old Java Object) with no
dependency on Spring Web Flow.
<br>
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.
<br>
If the target method accepts arguments they may be specified in order by using the
'method-arguments' sub-element.
<br>
If the target method returns a value that value may be exposed to the flow using
the 'method-result' sub-element.
<br>
For example:
<pre>
&lt;bean-action bean="orderClerk" method="placeOrder"&gt;
&lt;method-arguments&gt;
&lt;argument expression="flowScope.order"/&gt;
&lt;/method-arguments&gt;
&lt;method-result name="orderConfirmation"/&gt;
&lt;/bean-action&gt;
</pre>
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".
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="evaluate-action">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Defines an arbitrary expression against the flow request context to evaluate as an action.
<br>
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.
<br>
For example:
<pre>
&lt;evaluate-action expression="flowScope.interview.nextQuestion()"&gt;
&lt;evaluation-result name="question"/&gt;
&lt;/evaluate-action&gt;
</pre>
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.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="render">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Requests that a specific fragment of the next view be rendered instead of the entire view.
Multiple fragments may be specified using a comma delimiter.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="set">
<xsd:annotation>
<xsd:documentation>
@@ -184,7 +121,7 @@ This action always returns a "success" event unless an exception is thrown.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:element>
</xsd:choice>
</xsd:group>
@@ -227,7 +164,7 @@ A flow may also exhibit the following characteristics:
(See the &lt;exception-handler/&gt; element)
<li>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 &lt;import/&gt; element)
</ul>
@@ -288,14 +225,6 @@ that launched this flow.
<li>The 'target' of each mapping is this flow execution's RequestContext, exposing access to
data structures such as 'flowScope'.
</ul>
<br>
For example:
<pre>
&lt;input-mapper&gt;
&lt;input-attribute name="id"/&gt;
&lt;/input-mapper&gt;
</pre>
... maps the value of "id" input attribute to the "id" attribute in this flow's scope.
]]>
</xsd:documentation>
</xsd:annotation>
@@ -440,14 +369,6 @@ internal data structures such as 'flowScope'.
<li>The 'target' of each mapping is the flow output map that will contain the output returned to the
caller that launched this flow.
</ul>
<br>
For example:
<pre>
&lt;output-mapper&gt;
&lt;mapping source="flowScope.myFlowAttribute" target="clientOutputAttribute"/&gt;
&lt;/output-mapper&gt;
</pre>
... maps the value of "myFlowAttribute" in flow scope to "clientOutputAttribute" in this flow's output map.
]]>
</xsd:documentation>
</xsd:annotation>
@@ -646,7 +567,7 @@ fully-qualified class (e.g. 'java.lang.Integer'). The class cannot be abstract
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="scope" type="scopeType" default="default">
<xsd:attribute name="scope" type="scopeType">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
@@ -690,7 +611,7 @@ An attribute describing this state.
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Indicates this state is secured
Indicates this state is secured.
]]>
</xsd:documentation>
</xsd:annotation>
@@ -743,304 +664,8 @@ execution of this flow definition. Exception handlers may be attached at the sta
</xsd:complexType>
</xsd:element>
<xsd:element name="action">
<xsd:element name="evaluate">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="attribute" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
An attribute describing this action.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="bean" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The identifier of the action implementation to execute, typically the id of a bean
registered in a Spring BeanFactory.
<br>
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.
<br>
This is similar to the &lt;ref bean="myBean"/&gt; notation of the Spring beans DTD.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="name" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
An optional name qualifier for this action. When specified this action will
qualify execution result event identifiers by this name. For example, if this
action is named "placeOrder" and signals a "success" result event after execution,
the fully qualified result event the flow can respond to would be "placeOrder.success".
<br>
This can be used to execute actions in an ordered chain, where the flow responds
to the the last action result in the chain:
<pre>
&lt;action-state id="setupForm"&gt;
&lt;action name="setupForm" bean="formAction" method="setupForm"/&gt;
&lt;action name="loadReferenceData" bean="formAction" method="loadReferenceData"/&gt;
&lt;transition on="loadReferenceData.success" to="displayForm"&gt;
&lt;/action-state&gt;
</pre>
... will execute 'setupForm' followed by 'loadRefenceData', then transition the flow to
the 'displayForm' state on a successful 'loadReferenceData' invocation.
<br>
An action with a name is often referred to as a "named action".
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="method" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The name of the method to invoke on this action.
<br>
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:
<pre>
public Event &lt;methodName&gt;(RequestContext context);
</pre>
As an example:
<pre>
&lt;action bean="formAction" method="setupForm"/&gt;
</pre>
... might invoke:
<pre>
public class FormAction extends MultiAction {
public Event setupForm(RequestContext context) {
return success();
}
}
</pre>
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="bean-action">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="attribute" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
An attribute describing this bean action.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="method-arguments" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Defines the expressions used to resolve the arguments to the bean-action method to invoke.
Use this when the target method declares one or more parameters.
The order of the argument expression list is significant.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="method-result" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The rule for how the return value of the invoked bean method should be exposed to the flow.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="bean" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The identifier of the the bean to invoke, typically a singleton bean instance registered in
the Spring BeanFactory.
<br>
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.
<br>
This is similar to the &lt;ref bean="myBean"/&gt; notation of the Spring beans DTD.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="name" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
An optional name qualifier for this bean action. When specified this action will
qualify execution result event identifiers by this name. For example, if this action
is named "placeOrder" and signals a "success" result event after execution, the
fully qualified result event the flow can respond to would be "placeOrder.success".
<br>
This can be used to execute actions in an ordered chain, where the flow responds
to the the last action result in the chain:
<pre>
&lt;action-state id="setupForm"&gt;
&lt;action name="setupForm" bean="formAction" method="setupForm"/&gt;
&lt;action name="loadReferenceData" bean="formAction" method="loadReferenceData"/&gt;
&lt;transition on="loadReferenceData.success" to="displayForm"&gt;
&lt;/action-state&gt;
</pre>
... will execute 'setupForm' followed by 'loadRefenceData', then transition the flow to
the 'displayForm' state on a successful 'loadReferenceData' invocation.
<br>
An action with a name is often referred to as a "named action".
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="method" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The name of the method to invoke on this bean.
<br>
If the method has parameters the arguments to those parameters should be specified using
the 'method-arguments' element.
<br>
If the method returns a value that should be exposed to this flow, the 'method-result' element
should be specified.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="method-arguments">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="argument" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
An argument expression for this bean method. The expression is evaluated against
this flow's request context to resolve the argument value.
<br>
Typically used to pass a value from a flow scope type into this bean method as an argument.
<br>
Examples:
<pre>
&lt;argument expression="flowScope.order"/&gt;
</pre>
... passes in the value of the 'order' attribute in flow scope.
<pre>
&lt;argument expression="'a constant'"/&gt;
</pre>
... passes in the 'a constant' literal.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="argument">
<xsd:complexType>
<xsd:attribute name="expression" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The value expression for this bean method argument.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="parameter-type" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The method parameter type. Optional. If specified and the argument value does not equal the
parameter type, a type conversion will be attempted.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="method-result">
<xsd:complexType>
<xsd:attribute name="name" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
An expression used to expose the return value of the target bean-action method, such as "#{flowScope.myResult}".
Can be a simple literal when used in conjunction with the 'scope' attribute.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="scope" type="scopeType" use="optional">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The scope of the attribute that will expose the return value of the invoked bean method.
The available scope types are:
<ol>
<li>request - The result goes out of scope when the call into this flow that invoked this method completes.
<li>flash - The result goes out of scope when the next user event is signaled.
<li>flow - The result goes out of scope when this local flow session ends.
<li>conversation - The result goes out of scope when the overall conversation governing this flow execution ends.
</ol>
<br>
If not specified then the name attribute must be a fully resolvable expression such as "#{flowScope.myResult}".
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="type" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The desired method result type. Optional. If specified and the method return value is not of the
desired result type, a type conversion will be attempted.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="evaluate-action">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="attribute" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
An attribute describing this bean action.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="evaluation-result" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The rule for how the evaluated expression result should be exposed to the flow. Use of this sub-element is deprecated in
favor of the newer "result" and "type" attributes and will be removed in a future release.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="expression" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
@@ -1059,7 +684,7 @@ A fully resolvable expression such as #{flowScope.foo} or #{bean.bar} for exposi
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="type" type="xsd:string" use="optional">
<xsd:attribute name="result-type" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
@@ -1069,123 +694,31 @@ desired result type, a type conversion will be attempted.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="name" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
An optional name qualifier for this evaluate action. When specified this action will
qualify execution result event identifiers by this name. For example, if this
action is named "firstInterviewQuestion" and signals a "success" result event after execution,
the fully qualified result event the flow can respond to would be "firstInterviewQuestion.success".
<br>
This can be used to execute actions in an ordered chain, where the flow responds
to the the last action result in the chain:
<pre>
&lt;action-state id="setupForm"&gt;
&lt;evaluate-action name="firstInterviewQuestion" bean="flowScope.interview.firstQuestion()"/&gt;
&lt;action name="setupForm" bean="formAction" method="setupForm"/&gt;
&lt;transition on="setupForm.success" to="displayForm"&gt;
&lt;/action-state&gt;
</pre>
... will execute 'firstInterviewQuestion' followed by 'setupForm', then transition the flow to
the 'displayForm' state on a successful 'setupForm' invocation.
<br>
An action with a name is often referred to as a "named action".
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="evaluation-result">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
This element is deprecated in favor of the newer "result" and "type" attributes on the <evaluate-action> and will
be removed in a future release.
<xsd:element name="render">
<xsd:complexType>
<xsd:attribute name="fragments" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The fragments of the next view to render. Multiple fragments may be specified by using the comma delimiter.
]]>
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="name" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The name of the attribute that will expose the result of expression evaluation.
It may be used in conjunction with the 'scope' attribute, or else it may be a fully resolvable
expression such as #{flowScope.foo}.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="scope" type="scopeType" use="optional">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The scope of the attribute that will expose the result of expression evaluation.
The available scope types are:
<ol>
<li>request - The result goes out of scope when the call into this flow that evaluated this expression completes.
<li>flash - The result goes out of scope when the next user event is signaled.
<li>flow - The result goes out of scope when this local flow session ends.
<li>conversation - The result goes out of scope when the overall conversation governing this flow execution ends.
</ol>
<br>
If not specified the name attribute must be a fully resolvable expression such as "#{flowScope.foo}".
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="type" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The desired evaluation result type. Optional. If specified and the method return value is not of the
desired result type, a type conversion will be attempted.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:complexType>
</xsd:element>
<xsd:element name="set">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="attribute" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
An attribute describing this bean action.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="attribute" type="xsd:string" use="required">
<xsd:attribute name="name" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The name of the attribute to set. May be a nested path using Java beans notation (e.g. bean.property) to set a property
on a bean in the specified scope, or it may be a fully resolvable expression such as "#{flowScope.foo}" instead.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="scope" type="scopeType" use="optional">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The scope the attribute value will be stored in. The available scope types are:
<ol>
<li>request - The attribute goes out of scope when the call into this flow that sets the attribute completes.
<li>flash - The attribute goes out of scope when the next user event is signaled.
<li>flow - The attribute goes out of scope when this local flow session ends.
<li>conversation - The attribute goes out of scope when the overall conversation governing this flow execution ends.
</ol>
<br>
If not specified the attribute to set must be a fully resolvable expression such as "#{flowScope.foo}".
]]>
</xsd:documentation>
</xsd:annotation>
@@ -1195,32 +728,6 @@ If not specified the attribute to set must be a fully resolvable expression such
<xsd:documentation>
<![CDATA[
The attribute value expression.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="name" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
An optional name qualifier for this set action. When specified this action will
qualify execution result event identifiers by this name. For example, if this
action is named "setFormSubmitted" and signals a "success" result event after execution,
the fully qualified result event the flow can respond to would be "setFormSubmitted.success".
<br>
This can be used to execute actions in an ordered chain, where the flow responds
to the the last action result in the chain:
<pre>
&lt;action-state id="processSubmit"&gt;
&lt;action name="processSubmit" bean="formAction" method="processSubmit"/&gt;
&lt;set name="setFormSubmitted" attribute="formSubmitted" value="true"/&gt;
&lt;transition on="setFormSubmitted.success" to="thankYou"&gt;
&lt;/action-state&gt;
</pre>
... will execute 'processSubmit' followed by 'setFormSubmitted', then transition the flow to
the 'thankYou' state on a successful 'setFormSubmitted' invocation.
<br>
An action with a name is often referred to as a "named action".
]]>
</xsd:documentation>
</xsd:annotation>
@@ -1638,32 +1145,38 @@ Defines state entry logic to be executed. This logic will always execute when th
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="attribute-mapper" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Defines an attribute mapper to map attributes to and/or from the subflow. Input attributes
may be exposed to the subflow when it is started by using an &lt;input-mapper/&gt;.
Output attributes may be mapped from the subflow when it ends by using an &lt;output-mapper/&gt;.
<br>
For the input mapper the following mapping characteristics apply:
<ul>
<li>The 'source' of each mapping is the RequestContext, exposing access to internal
data structures of this flow such as flowScope.
<li>The 'target' of each input mapping is the subflow's input Map.
</ul>
<br>
For the output mapper the following mapping characteristics apply:
<ul>
<li>The 'source' of each output mapping is the subflow's output Map.
<li>The 'target' of each output mapping is the RequestContext, exposing access to
internal data structures of this flow such as flowScope.
</ul>
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:element>
<xsd:element ref="input-mapper" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Maps data from this flow to the subflow as input. The following mapping characteristics apply:
<ul>
<li>The 'source' of each mapping is this flow execution's RequestContext, exposing access to
internal data structures such as 'flowScope'.
<li>The 'target' of each mapping is the input map that will be passed to the subflow.
</ul>
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="output-mapper" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The mapper that will map flow output attributes when this flow ends by entering this
end state. Output attributes act as flow return values.
<br>
For the output mapper the following mapping characteristics apply:
<ul>
<li>The 'source' of each mapping is a Map containing all the output returned by the subflow.
<li>The 'target' of each mapping is this flow execution's RequestContext, exposing access to
data structures such as 'flowScope'.
</ul>
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="transition" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
@@ -1697,7 +1210,7 @@ execution of this flow definition. Exception handlers may be attached at the sta
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="flow" type="xsd:string" use="required">
<xsd:attribute name="subflow" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
@@ -1706,79 +1219,21 @@ The id of the flow to be spawned as a subflow when this subflow state is entered
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="subflow-attribute-mapper" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The id of a custom bean to use to map attributes to and from the subflow. Specifiy this attribute or one of the
input-mapper/output-mapper sub-elements, not both.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="attribute-mapper">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="input-mapper" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The mapper to map input attributes to the subflow when it is started.
The following mapping characteristics apply:
<ul>
<li>The 'source' of each mapping is the RequestContext, exposing access to internal
data structures of this flow such as flowScope.
<li>The 'target' of each input mapping is the subflow's input map.
</ul>
<br>
For example:
<pre>
&lt;input-mapper&gt;
&lt;mapping source="flowScope.myFlowAttribute" target="subflowInputAttribute"/&gt;
&lt;/input-mapper&gt;
</pre>
... maps the value of "flowScope.myFlowAttribute" to the "subflowInputAttribute" in the subflow input map.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="output-mapper" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The mapper to map output attributes from the subflow when it ends.
For the output mapper the following mapping characteristics apply:
<ul>
<li>The 'source' of each output mapping is the subflow's output map.
<li>The 'target' of each output mapping is the RequestContext, exposing access to
internal data structures of this flow such as flowScope.
</ul>
<br>
For example:
<pre>
&lt;output-mapper&gt;
&lt;mapping source="aSubflowOutputAttribute" target="flowScope.myFlowAttribute"/&gt;
&lt;/output-mapper&gt;
</pre>
... maps the value of "aSubflowOutputAttribute" in the subflow output map to "myFlowAttribute"
in flow scope.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="bean" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The identifier of a custom flow attribute mapper implementation exported in the
Spring bean factory. This is similar to the &lt;ref bean="myBean"/&gt; notation of the Spring beans DTD.
<br>
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.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="end-state">
<xsd:complexType>
<xsd:complexContent>
@@ -1825,14 +1280,6 @@ internal data structures such as 'flowScope'.
<li>The 'target' of each mapping is the flow output map that will contain the output returned to the
caller that launched this flow.
</ul>
<br>
For example:
<pre>
&lt;output-mapper&gt;
&lt;mapping source="flowScope.myFlowAttribute" target="clientOutputAttribute"/&gt;
&lt;/output-mapper&gt;
</pre>
... maps the value of "myFlowAttribute" in flow scope to "clientOutputAttribute" in this flow's output map.
]]>
</xsd:documentation>
</xsd:annotation>
@@ -1891,13 +1338,23 @@ context.
The exact semantics regarding the interpretation of this value are determined by the
installed TextToViewSelector converter.
<br>
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.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:attribute>
<xsd:attribute name="commit" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Indicates if this end-state is a "commit state". If true, any changes made to managed entities attached to the flow's persistence context
will be flushed in a system transaction when this state is reached. If false, no flush will occur. This attribute has no effect
if the flow is not a persistence-context.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>

View File

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

View File

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

View File

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

View File

@@ -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.
*/

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<!-- Appenders -->
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<param name="Target" value="System.out" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p: %c - %m%n" />
</layout>
</appender>
<logger name="org.springframework.beans">
<level value="warn" />
</logger>
<logger name="org.springframework.jdbc">
<level value="warn" />
</logger>
<logger name="org.springframework.transaction">
<level value="warn" />
</logger>
<logger name="org.springframework.orm">
<level value="warn" />
</logger>
<logger name="org.springframework.web">
<level value="debug" />
</logger>
<logger name="org.springframework.webflow">
<level value="debug" />
</logger>
<!-- Root Logger -->
<root>
<priority value="warn" />
<appender-ref ref="console" />
</root>
</log4j:configuration>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,14 @@
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<start-actions>
<evaluate expression="action"/>
<evaluate expression="action" result="result"/>
<evaluate expression="action" result="result" result-type="integer"/>
<evaluate expression="multiAction.actionMethod" result="result" result-type="integer"/>
</start-actions>
<end-state id="end" />
</flow>

View File

@@ -0,0 +1,11 @@
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<start-actions>
<evaluate expression="testBean.foo"/>
</start-actions>
<end-state id="end"/>
</flow>

View File

@@ -4,33 +4,26 @@
xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<view-state id="enterCriteria" view="searchCriteria">
<render-actions>
<action bean="formAction" method="setupForm"/>
<render-actions>
<evaluate expression="formAction.setupForm" />
</render-actions>
<transition on="search" to="displayResults">
<action bean="formAction" method="bindAndValidate"/>
<evaluate expression="formAction.bindAndValidate" />
</transition>
</view-state>
<view-state id="displayResults" view="searchResults">
<render-actions>
<bean-action bean="phonebook" method="search">
<method-arguments>
<argument expression="${flowScope.searchCriteria}"/>
</method-arguments>
<method-result name="results"/>
</bean-action>
<render-actions>
<evaluate expression="phonebook.search(searchCriteria)" result="results" />
</render-actions>
<transition on="newSearch" to="enterCriteria"/>
<transition on="select" to="browseDetails"/>
</view-state>
<subflow-state id="browseDetails" flow="detail-flow">
<attribute-mapper>
<input-mapper>
<mapping source="${requestParameters.id}" target="${id}" from="string" to="long"/>
</input-mapper>
</attribute-mapper>
<subflow-state id="browseDetails" subflow="detail-flow">
<input-mapper>
<mapping source="${requestParameters.id}" target="${id}" from="string" to="long"/>
</input-mapper>
<transition on="finish" to="displayResults"/>
</subflow-state>