Merging of SWF-367 back into trunk. This has all of the SWF 2.0 goodies.

This commit is contained in:
Ben Hale
2007-10-11 01:16:37 +00:00
parent b9513cd42b
commit 58d1ea3acc
984 changed files with 11849 additions and 39596 deletions

View File

@@ -2,49 +2,49 @@
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
<classpathentry combineaccessrules="false" kind="src" path="/spring-binding"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="lib/buildtime/commons-beanutils.jar"/>
<classpathentry kind="lib" path="lib/buildtime/commons-digester.jar"/>
<classpathentry kind="lib" path="lib/buildtime/concurrent.jar"/>
<classpathentry kind="lib" path="lib/test/easymock.jar"/>
<classpathentry kind="lib" path="lib/buildtime/hibernate.jar"/>
<classpathentry kind="lib" path="lib/buildtime/junit.jar"/>
<classpathentry kind="lib" path="lib/global/ognl.jar"/>
<classpathentry kind="lib" path="lib/buildtime/portlet-api.jar"/>
<classpathentry kind="lib" path="lib/buildtime/servlet-api.jar"/>
<classpathentry kind="lib" path="lib/global/spring-beans.jar" sourcepath="/spring/src"/>
<classpathentry kind="lib" path="lib/global/spring-context.jar" sourcepath="/spring/src"/>
<classpathentry kind="lib" path="lib/global/spring-core.jar" sourcepath="/spring/src"/>
<classpathentry kind="lib" path="lib/global/spring-web.jar" sourcepath="/spring/src"/>
<classpathentry kind="lib" path="lib/buildtime/spring-webmvc.jar" sourcepath="/spring/src"/>
<classpathentry kind="lib" path="lib/buildtime/struts.jar"/>
<classpathentry kind="lib" path="lib/buildtime/commons-collections.jar"/>
<classpathentry kind="lib" path="lib/buildtime/dom4j.jar"/>
<classpathentry kind="lib" path="lib/test/hsqldb.jar"/>
<classpathentry kind="lib" path="lib/buildtime/jta.jar"/>
<classpathentry kind="lib" path="lib/test/spring-jdbc.jar"/>
<classpathentry kind="lib" path="lib/buildtime/persistence-api.jar"/>
<classpathentry kind="lib" path="lib/test/clover.jar"/>
<classpathentry kind="lib" path="lib/test/hibernate-annotations.jar"/>
<classpathentry kind="lib" path="lib/test/hibernate-commons-annotations.jar"/>
<classpathentry kind="lib" path="lib/test/hibernate-entitymanager.jar"/>
<classpathentry kind="lib" path="lib/test/javassist.jar"/>
<classpathentry kind="lib" path="lib/test/jboss-common-core.jar"/>
<classpathentry kind="lib" path="lib/buildtime/spring-orm.jar"/>
<classpathentry kind="lib" path="lib/buildtime/antlr.jar"/>
<classpathentry kind="lib" path="lib/buildtime/asm.jar"/>
<classpathentry kind="lib" path="lib/buildtime/asm-attrs.jar"/>
<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/buildtime/commons-collections.jar"/>
<classpathentry kind="lib" path="lib/buildtime/commons-digester.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"/>
<classpathentry kind="lib" path="lib/buildtime/concurrent.jar"/>
<classpathentry kind="lib" path="lib/test/easymock.jar"/>
<classpathentry kind="lib" path="lib/buildtime/dom4j.jar"/>
<classpathentry kind="lib" path="lib/buildtime/ehcache.jar"/>
<classpathentry kind="lib" path="lib/buildtime/el-api.jar"/>
<classpathentry kind="lib" path="lib/buildtime/hibernate.jar"/>
<classpathentry kind="lib" path="lib/test/hibernate-annotations.jar"/>
<classpathentry kind="lib" path="lib/test/hibernate-commons-annotations.jar"/>
<classpathentry kind="lib" path="lib/test/hibernate-entitymanager.jar"/>
<classpathentry kind="lib" path="lib/test/hsqldb.jar"/>
<classpathentry kind="lib" path="lib/test/javassist.jar"/>
<classpathentry kind="lib" path="lib/test/jboss-common-core.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/buildtime/oro.jar"/>
<classpathentry kind="lib" path="lib/buildtime/spring-tx.jar"/>
<classpathentry kind="lib" path="lib/buildtime/spring-webmvc-portlet.jar"/>
<classpathentry kind="lib" path="lib/buildtime/xalan.jar"/>
<classpathentry kind="lib" path="lib/buildtime/persistence-api.jar"/>
<classpathentry kind="lib" path="lib/buildtime/portlet-api.jar"/>
<classpathentry kind="lib" path="lib/buildtime/servlet-api.jar"/>
<classpathentry kind="lib" path="lib/global/spring-beans.jar"/>
<classpathentry kind="lib" path="lib/global/spring-binding.jar"/>
<classpathentry kind="lib" path="lib/global/spring-context.jar"/>
<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/test/spring-test.jar"/>
<classpathentry kind="lib" path="lib/buildtime/spring-webmvc-struts.jar"/>
<classpathentry kind="lib" path="lib/buildtime/spring-tx.jar"/>
<classpathentry kind="lib" path="lib/global/spring-web.jar"/>
<classpathentry kind="lib" path="lib/buildtime/spring-webmvc.jar"/>
<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="output" path="target/classes"/>
</classpath>

View File

@@ -48,6 +48,7 @@
<!-- build time only dependencies -->
<dependency org="javax.servlet" name="servlet-api" rev="2.4" conf="buildtime->default" />
<dependency org="javax.portlet" name="portlet-api" rev="1.0" conf="buildtime->default" />
<dependency org="javax.el" name="el-api" rev="1.0" conf="buildtime->default" />
<dependency org="org.hibernate" name="hibernate" rev="3.2.4.ga" conf="buildtime->default" />
<dependency org="org.springframework" name="spring-orm" rev="2.1-m4" conf="buildtime->default" />
<dependency org="org.springframework" name="spring-tx" rev="2.1-m4" conf="buildtime->default" />

View File

@@ -1,4 +1,4 @@
SPRING WEB FLOW 2.0-m2 (October 2007)
SPRING WEB FLOW 2.0-M2 (October 2007)
----------------------------------
http://www.springframework.org/webflow
http://forum.springframework.org

View File

@@ -1,2 +1 @@
http\://www.springframework.org/schema/webflow-config/spring-webflow-config-1.0.xsd=org/springframework/webflow/config/spring-webflow-config-1.0.xsd
http\://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd=org/springframework/webflow/config/spring-webflow-config-2.0.xsd

View File

@@ -24,7 +24,6 @@ import org.springframework.webflow.core.collection.AttributeMap;
import org.springframework.webflow.execution.Action;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.support.EventFactorySupport;
/**
* Base action that provides assistance commonly needed by action implementations. This includes:
@@ -62,7 +61,7 @@ public abstract class AbstractAction implements Action, InitializingBean {
}
/**
* Action initializing callback, may be overriden by subclasses to perform custom initialization logic.
* Action initializing callback, may be overridden by subclasses to perform custom initialization logic.
* <p>
* Keep in mind that this hook will only be invoked when this action is deployed in a Spring application context
* since it uses the Spring {@link InitializingBean} mechanism to trigger action initialisation.

View File

@@ -15,7 +15,6 @@
*/
package org.springframework.webflow.action;
import org.springframework.binding.expression.EvaluationContext;
import org.springframework.binding.expression.Expression;
import org.springframework.util.Assert;
import org.springframework.webflow.execution.Event;
@@ -70,20 +69,10 @@ public class EvaluateAction extends AbstractAction {
}
protected Event doExecute(RequestContext context) throws Exception {
Object result = expression.evaluate(context, getEvaluationContext(context));
Object result = expression.getValue(context);
if (evaluationResultExposer != null) {
evaluationResultExposer.exposeResult(result, context);
}
return resultEventFactorySelector.forResult(result).createResultEvent(this, result, context);
}
/**
* Template method subclasses may override to customize the expressin evaluation context. This implementation
* returns null.
* @param context the request context
* @return the evaluation context
*/
protected EvaluationContext getEvaluationContext(RequestContext context) {
return null;
}
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.webflow.execution.support;
package org.springframework.webflow.action;
import org.springframework.webflow.core.collection.AttributeMap;
import org.springframework.webflow.core.collection.CollectionUtils;

View File

@@ -0,0 +1,23 @@
package org.springframework.webflow.action;
import org.springframework.binding.expression.Expression;
import org.springframework.util.Assert;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
public class ExternalRedirectAction extends AbstractAction {
private Expression resourceUri;
public ExternalRedirectAction(Expression resourceUri) {
Assert.notNull(resourceUri, "The URI of the resource to redirect to is required");
this.resourceUri = resourceUri;
}
protected Event doExecute(RequestContext context) throws Exception {
String resourceUri = (String) this.resourceUri.getValue(context);
context.getExternalContext().sendExternalRedirect(resourceUri);
return success();
}
}

View File

@@ -0,0 +1,70 @@
package org.springframework.webflow.action;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.springframework.binding.expression.Expression;
import org.springframework.util.Assert;
import org.springframework.webflow.context.FlowDefinitionRequestInfo;
import org.springframework.webflow.context.RequestPath;
import org.springframework.webflow.core.collection.LocalParameterMap;
import org.springframework.webflow.core.collection.ParameterMap;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
public class FlowDefinitionRedirectAction extends AbstractAction {
private Expression flowId;
private Expression[] requestElements;
private Map requestParameters;
public FlowDefinitionRedirectAction(Expression flowId, Expression[] requestElements, Map requestParameters) {
Assert.notNull(flowId, "The flow id to redirect to is required");
this.flowId = flowId;
this.requestElements = requestElements;
this.requestParameters = requestParameters;
}
protected Event doExecute(RequestContext context) throws Exception {
String flowId = (String) this.flowId.getValue(context);
RequestPath requestPath = evaluateRequestPath(context);
ParameterMap requestParameters = evaluateRequestParameters(context);
context.getExternalContext().sendFlowDefinitionRedirect(
new FlowDefinitionRequestInfo(flowId, requestPath, requestParameters, null));
return success();
}
private RequestPath evaluateRequestPath(RequestContext context) {
if (this.requestElements == null || this.requestElements.length == 0) {
return null;
}
String[] requestElements = new String[this.requestElements.length];
for (int i = 0; i < this.requestElements.length; i++) {
Expression element = this.requestElements[i];
requestElements[i] = (String) element.getValue(context);
}
return RequestPath.valueOf(requestElements);
}
private ParameterMap evaluateRequestParameters(RequestContext context) {
if (this.requestParameters == null) {
return null;
} else {
Map requestParameters = new HashMap();
for (Iterator it = this.requestParameters.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
Expression name = (Expression) entry.getKey();
Expression value = (Expression) entry.getValue();
String paramName = (String) name.getValue(context);
String paramValue = (String) value.getValue(context);
requestParameters.put(paramName, paramValue);
}
return new LocalParameterMap(requestParameters);
}
}
public static FlowDefinitionRedirectAction create(String encodedFlowRedirect) {
// TODO
return null;
}
}

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.webflow.action;
import java.lang.reflect.Method;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.beans.PropertyEditorRegistry;
@@ -26,7 +24,7 @@ import org.springframework.core.style.StylerUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.DataBinder;
import org.springframework.validation.Errors;
import org.springframework.validation.MessageCodesResolver;
@@ -36,7 +34,6 @@ import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.ScopeType;
import org.springframework.webflow.util.DispatchMethodInvoker;
import org.springframework.webflow.util.ReflectionUtils;
/**
* Multi-action that implements common logic dealing with input forms. This class leverages the Spring Web data binding
@@ -242,20 +239,6 @@ import org.springframework.webflow.util.ReflectionUtils;
*/
public class FormAction extends MultiAction implements InitializingBean {
/*
* Implementation note: Uses deprecated DataBinder.getErrors() to remain compatible with Spring 1.2.x.
*/
/*
* Implementation note: Introspects BindException at class init time to preserve 1.2.x compatability.
*/
private static boolean hasPropertyEditorRegistryAccessor;
static {
hasPropertyEditorRegistryAccessor = ClassUtils
.hasMethod(BindException.class, "getPropertyEditorRegistry", null);
}
/**
* The default form object name ("formObject").
*/
@@ -510,7 +493,7 @@ public class FormAction extends MultiAction implements InitializingBean {
if (logger.isDebugEnabled()) {
logger.debug("Executing validation");
}
doValidate(context, formObject, binder.getErrors());
doValidate(context, formObject, binder.getBindingResult());
} else {
if (logger.isDebugEnabled()) {
if (getValidator() == null) {
@@ -520,8 +503,8 @@ public class FormAction extends MultiAction implements InitializingBean {
}
}
}
putFormErrors(context, binder.getErrors());
return binder.getErrors().hasErrors() ? error() : success();
putFormErrors(context, binder.getBindingResult());
return binder.getBindingResult().hasErrors() ? error() : success();
}
/**
@@ -542,8 +525,8 @@ public class FormAction extends MultiAction implements InitializingBean {
Object formObject = getFormObject(context);
DataBinder binder = createBinder(context, formObject);
doBind(context, binder);
putFormErrors(context, binder.getErrors());
return binder.getErrors().hasErrors() ? error() : success();
putFormErrors(context, binder.getBindingResult());
return binder.getBindingResult().hasErrors() ? error() : success();
}
/**
@@ -635,7 +618,7 @@ public class FormAction extends MultiAction implements InitializingBean {
if (logger.isDebugEnabled()) {
logger.debug("Creating new form errors for object with name '" + getFormObjectName() + "'");
}
Errors errors = createBinder(context, formObject).getErrors();
Errors errors = createBinder(context, formObject).getBindingResult();
putFormErrors(context, errors);
return errors;
}
@@ -691,8 +674,8 @@ public class FormAction extends MultiAction implements InitializingBean {
*/
private boolean formErrorsValid(RequestContext context, Object formObject) {
Errors errors = getFormObjectAccessor(context).getFormErrors(getFormObjectName(), getFormErrorsScope());
if (errors instanceof BindException) {
BindException be = (BindException) errors;
if (errors instanceof BindingResult) {
BindingResult be = (BindingResult) errors;
if (be.getTarget() != formObject) {
if (logger.isInfoEnabled()) {
logger.info("Inconsistency detected: the Errors instance in '" + getFormErrorsScope()
@@ -715,29 +698,9 @@ public class FormAction extends MultiAction implements InitializingBean {
* @param context the flow execution request context
*/
private void reinstallPropertyEditors(RequestContext context) {
BindException errors = (BindException) getFormObjectAccessor(context).getFormErrors(getFormObjectName(),
BindingResult errors = (BindingResult) getFormObjectAccessor(context).getFormErrors(getFormObjectName(),
getFormErrorsScope());
registerPropertyEditors(context, getPropertyEditorRegistry(errors));
}
/**
* Obtain a property editor registry from given bind exception (errors instance).
*/
private PropertyEditorRegistry getPropertyEditorRegistry(BindException errors) {
Method accessor;
try {
if (hasPropertyEditorRegistryAccessor) {
accessor = errors.getClass().getMethod("getPropertyEditorRegistry", null);
} else {
// only way to get at the registry in 1.2.8 or <.
accessor = errors.getClass().getDeclaredMethod("getBeanWrapper", null);
accessor.setAccessible(true);
}
} catch (NoSuchMethodException e) {
throw new IllegalStateException(
"Unable to resolve property editor registry accessor method as expected - this should not happen");
}
return (PropertyEditorRegistry) ReflectionUtils.invokeMethod(accessor, errors);
registerPropertyEditors(context, errors.getPropertyEditorRegistry());
}
/**
@@ -852,8 +815,8 @@ public class FormAction extends MultiAction implements InitializingBean {
if (logger.isDebugEnabled()) {
logger.debug("Binding completed for form object with name '" + binder.getObjectName()
+ "', post-bind formObject toString = " + binder.getTarget());
logger.debug("There are [" + binder.getErrors().getErrorCount() + "] errors, details: "
+ binder.getErrors().getAllErrors());
logger.debug("There are [" + binder.getBindingResult().getErrorCount() + "] errors, details: "
+ binder.getBindingResult().getAllErrors());
}
}

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.webflow.action;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.ScopeType;
@@ -24,10 +24,10 @@ import org.springframework.webflow.execution.ScopeType;
* Convenience helper that encapsulates logic on how to retrieve and expose form objects and associated errors to and
* from a flow execution request context.
* <p>
* <b>Note</b>: The form object available under the well known attribute name {@link #CURRENT_FORM_OBJECT_ATTRIBUTE}
* will be the last ("current") form object set in the request context. The same is true for the associated errors
* object. This implies that special care should be taken when accessing the form object using this alias if there are
* multiple form objects available in the flow execution request context!
* <b>Note</b>: The form object available under the well known attribute name will be the last ("current") form object
* set in the request context. The same is true for the associated errors object. This implies that special care should
* be taken when accessing the form object using this alias if there are multiple form objects available in the flow
* execution request context!
*
* @see org.springframework.webflow.execution.RequestContext
* @see org.springframework.validation.Errors
@@ -53,8 +53,7 @@ public class FormObjectAccessor {
/**
* The errors prefix.
*/
// use deprecated API to remain compatible with Spring 1.2.x
private static final String ERRORS_PREFIX = BindException.ERROR_KEY_PREFIX;
private static final String ERRORS_PREFIX = BindingResult.MODEL_KEY_PREFIX;
/**
* The wrapped request context.
@@ -86,8 +85,7 @@ public class FormObjectAccessor {
}
/**
* Gets the form object from the context, using the well-known attribute name {@link #CURRENT_FORM_OBJECT_ATTRIBUTE}.
* Will try all scopes.
* Gets the form object from the context, using the well-known attribute name. Will try all scopes.
* @return the form object, or null if not found
*/
public Object getCurrentFormObject() {
@@ -107,7 +105,7 @@ public class FormObjectAccessor {
}
/**
* Gets the form object from the context, using the well-known attribute name {@link #CURRENT_FORM_OBJECT_ATTRIBUTE}.
* Gets the form object from the context, using the well-known attribute name.
* @param scopeType the scope to obtain the form object from
* @return the form object, or null if not found
*/
@@ -116,8 +114,7 @@ public class FormObjectAccessor {
}
/**
* Expose given form object using the well known alias {@link #CURRENT_FORM_OBJECT_ATTRIBUTE} in the specified
* scope.
* Expose given form object using the well known alias in the specified scope.
* @param formObject the form object
* @param scopeType the scope in which to expose the form object
*/
@@ -160,8 +157,8 @@ public class FormObjectAccessor {
}
/**
* Gets the form object <code>Errors</code> tracker from the context, using the form object name
* {@link #CURRENT_FORM_OBJECT_ATTRIBUTE}. This method will search all scopes.
* Gets the form object <code>Errors</code> tracker from the context, using the form object name. This method will
* search all scopes.
* @return the form object Errors tracker, or null if not found
*/
public Errors getCurrentFormErrors() {
@@ -181,8 +178,7 @@ public class FormObjectAccessor {
}
/**
* Gets the form object <code>Errors</code> tracker from the context, using the form object name
* {@link #CURRENT_FORM_OBJECT_ATTRIBUTE}.
* Gets the form object <code>Errors</code> tracker from the context, using the form object name.
* @param scopeType the scope to obtain the errors from
* @return the form object Errors tracker, or null if not found
*/
@@ -191,8 +187,7 @@ public class FormObjectAccessor {
}
/**
* Expose given errors instance using the well known alias {@link #CURRENT_FORM_OBJECT_ATTRIBUTE} in the specified
* scope.
* Expose given errors instance using the well known alias in the specified scope.
* @param errors the errors instance
* @param scopeType the scope in which to expose the errors instance
*/
@@ -203,7 +198,7 @@ public class FormObjectAccessor {
/**
* Gets the form object <code>Errors</code> tracker from the context, using the specified form object name.
* @param formObjectName the name of the Errors object, which will be prefixed with
* {@link BindException#ERROR_KEY_PREFIX}
* {@link BindingResult#MODEL_KEY_PREFIX}
* @param scopeType the scope to obtain the errors from
* @return the form object errors instance, or null if not found
*/

View File

@@ -19,7 +19,6 @@ import org.springframework.core.JdkVersion;
import org.springframework.core.enums.LabeledEnum;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.support.EventFactorySupport;
/**
* Result object-to-event adapter interface that tries to do a sensible conversion of the result object into a web flow
@@ -31,13 +30,13 @@ import org.springframework.webflow.execution.support.EventFactorySupport;
* </tr>
* <tr>
* <td>null</td>
* <td>{@link org.springframework.webflow.execution.support.EventFactorySupport#getNullEventId()}</td>
* <td>{@link org.springframework.webflow.action.EventFactorySupport#getNullEventId()}</td>
* <td>&nbsp;</td>
* </tr>
* <tr>
* <td>{@link java.lang.Boolean} or boolean</td>
* <td>{@link org.springframework.webflow.execution.support.EventFactorySupport#getYesEventId()}/
* {@link org.springframework.webflow.execution.support.EventFactorySupport#getNoEventId()}</td>
* <td>{@link org.springframework.webflow.action.EventFactorySupport#getYesEventId()}/
* {@link org.springframework.webflow.action.EventFactorySupport#getNoEventId()}</td>
* <td>&nbsp;</td>
* </tr>
* <tr>

View File

@@ -15,9 +15,7 @@
*/
package org.springframework.webflow.action;
import org.springframework.binding.expression.EvaluationContext;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.SettableExpression;
import org.springframework.util.Assert;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.execution.Event;
@@ -34,7 +32,7 @@ public class SetAction extends AbstractAction {
/**
* The expression for setting the scoped attribute value.
*/
private SettableExpression attributeExpression;
private Expression attributeExpression;
/**
* The target scope.
@@ -52,7 +50,7 @@ public class SetAction extends AbstractAction {
* @param scope the target scope of the attribute
* @param valueExpression the evaluatable attribute value expression
*/
public SetAction(SettableExpression attributeExpression, ScopeType scope, Expression valueExpression) {
public SetAction(Expression attributeExpression, ScopeType scope, Expression valueExpression) {
Assert.notNull(attributeExpression, "The attribute expression is required");
Assert.notNull(scope, "The scope type is required");
Assert.notNull(valueExpression, "The value expression is required");
@@ -62,20 +60,9 @@ public class SetAction extends AbstractAction {
}
protected Event doExecute(RequestContext context) throws Exception {
EvaluationContext evaluationContext = getEvaluationContext(context);
Object value = valueExpression.evaluate(context, evaluationContext);
Object value = valueExpression.getValue(context);
MutableAttributeMap scopeMap = scope.getScope(context);
attributeExpression.evaluateToSet(scopeMap, value, evaluationContext);
attributeExpression.setValue(scopeMap, value);
return success();
}
/**
* Template method subclasses may override to customize the expression evaluation context. This implementation
* returns null.
* @param context the request context
* @return the evaluation context
*/
protected EvaluationContext getEvaluationContext(RequestContext context) {
return null;
}
}

View File

@@ -17,7 +17,6 @@ package org.springframework.webflow.action;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.support.EventFactorySupport;
/**
* Default implementation of the resultObject-to-event mapping interface. Always returns the "success" event.

View File

@@ -1,100 +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.config;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.config.MapFactoryBean;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.springframework.webflow.engine.support.ApplicationViewSelector;
import org.w3c.dom.Element;
/**
* {@link BeanDefinitionParser} for the <code>&lt;execution-attributes&gt;</code> tag.
*
* @author Ben Hale
*/
class ExecutionAttributesBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
// elements and attributes
private static final String ATTRIBUTE_ELEMENT = "attribute";
private static final String NAME_ATTRIBUTE = "name";
private static final String TYPE_ATTRIBUTE = "type";
private static final String VALUE_ATTRIBUTE = "value";
// properties
private static final String SOURCE_MAP_PROPERTY = "sourceMap";
protected Class getBeanClass(Element element) {
return MapFactoryBean.class;
}
protected void doParse(Element element, BeanDefinitionBuilder definitionBuilder) {
List attributeElements = DomUtils.getChildElementsByTagName(element, ATTRIBUTE_ELEMENT);
Map attributeMap = new ManagedMap(attributeElements.size());
putAttributes(attributeMap, attributeElements);
putSpecialAttributes(attributeMap, element);
definitionBuilder.addPropertyValue(SOURCE_MAP_PROPERTY, attributeMap);
}
/**
* Add all attributes defined in given list of attribute elements to given map.
*/
private void putAttributes(Map attributeMap, List attributeElements) {
for (Iterator i = attributeElements.iterator(); i.hasNext();) {
Element attributeElement = (Element) i.next();
String type = attributeElement.getAttribute(TYPE_ATTRIBUTE);
Object value;
if (StringUtils.hasText(type)) {
value = new TypedStringValue(attributeElement.getAttribute(VALUE_ATTRIBUTE), type);
} else {
value = attributeElement.getAttribute(VALUE_ATTRIBUTE);
}
attributeMap.put(attributeElement.getAttribute(NAME_ATTRIBUTE), value);
}
}
/**
* Add all non-generic (special) attributes defined in given element to given map.
*/
private void putSpecialAttributes(Map attributeMap, Element element) {
putAlwaysRedirectOnPauseAttribute(attributeMap, DomUtils.getChildElementByTagName(element,
ApplicationViewSelector.ALWAYS_REDIRECT_ON_PAUSE_ATTRIBUTE));
}
/**
* Parse the "alwaysRedirectOnPause" attribute from given element and add it to given map.
*/
private void putAlwaysRedirectOnPauseAttribute(Map attributeMap, Element element) {
if (element != null) {
Boolean value = Boolean.valueOf(element.getAttribute(VALUE_ATTRIBUTE));
attributeMap.put(ApplicationViewSelector.ALWAYS_REDIRECT_ON_PAUSE_ATTRIBUTE, value);
}
}
}

View File

@@ -0,0 +1,59 @@
package org.springframework.webflow.config;
import java.util.Collections;
import java.util.Set;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* A low-level pointer to a flow definition that will be registered in a flow registry and built by a concrete flow
* builder implementation class.
*
* @author Keith Donald
*/
class FlowBuilderInfo {
/**
* The id to assign to the flow definition.
*/
private String id;
/**
* The fully-qualified flow builder implementation class.
*/
private String className;
/**
* Attributes to assign to the flow definition.
*/
private Set attributes;
public FlowBuilderInfo(String id, String className, Set attributes) {
Assert.hasText(className, "The fully-qualified FlowBuilder class name is required");
this.className = className;
setId(id);
this.attributes = (attributes != null ? attributes : Collections.EMPTY_SET);
}
private void setId(String id) {
if (StringUtils.hasText(id)) {
this.id = id;
} else {
this.id = StringUtils.uncapitalize(StringUtils.delete(ClassUtils.getShortName(className), "FlowBuilder"));
}
}
public String getId() {
return id;
}
public String getClassName() {
return className;
}
public Set getAttributes() {
return attributes;
}
}

View File

@@ -0,0 +1,56 @@
package org.springframework.webflow.config;
import org.springframework.core.io.Resource;
import org.springframework.webflow.core.collection.AttributeMap;
/**
* An abstract representation of an externalized flow definition resource. Holds the data necessary to build a flow
* definition from an external file, and register the flow definition in a flow definition registry.
*
* Flow definition resources are created by a {@link FlowDefinitionResourceFactory}.
*
* @author Keith Donald
* @see FlowDefinitionResource
*/
public class FlowDefinitionResource {
private String id;
private Resource path;
private AttributeMap attributes;
FlowDefinitionResource(String flowId, Resource path, AttributeMap attributes) {
this.id = flowId;
this.path = path;
this.attributes = attributes;
}
/**
* Returns the identifier to be assigned to the flow definition.
* @return the flow definition identifier
*/
public String getId() {
return id;
}
/**
* Returns the path to the flow definition resource.
* @return the path location
*/
public Resource getPath() {
return path;
}
/**
* Returns attributes to assign the flow definition.
* @return flow definition attributes
*/
public AttributeMap getAttributes() {
return attributes;
}
public String toString() {
return path.toString();
}
}

View File

@@ -0,0 +1,79 @@
package org.springframework.webflow.config;
import java.io.File;
import java.io.IOException;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.Assert;
import org.springframework.webflow.core.collection.AttributeMap;
public class FlowDefinitionResourceFactory {
private ResourceLoader resourceLoader;
public FlowDefinitionResourceFactory() {
this.resourceLoader = new DefaultResourceLoader();
}
public FlowDefinitionResourceFactory(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "The resource loader cannot be null");
this.resourceLoader = resourceLoader;
}
public FlowDefinitionResource createResource(String path) {
return createResource(path, null, null);
}
public FlowDefinitionResource createResource(String path, AttributeMap attributes) {
return createResource(path, attributes, null);
}
public FlowDefinitionResource createResource(String path, AttributeMap attributes, String flowId) {
Resource resource = resourceLoader.getResource(path);
if (flowId == null || flowId.length() == 0) {
flowId = getFlowId(resource);
}
return new FlowDefinitionResource(flowId, resource, attributes);
}
public FlowDefinitionResource[] createResources(String pattern) throws IOException {
if (resourceLoader instanceof ResourcePatternResolver) {
ResourcePatternResolver resolver = (ResourcePatternResolver) resourceLoader;
Resource[] resources = resolver.getResources(pattern);
FlowDefinitionResource[] flowResources = new FlowDefinitionResource[resources.length];
for (int i = 0; i < resources.length; i++) {
Resource resource = resources[i];
flowResources[i] = new FlowDefinitionResource(getFlowId(resource), resource, null);
}
return flowResources;
} else {
throw new IllegalStateException(
"Cannot create flow definition resources from patterns without a ResourceLoader configured that is a ResourcePatternResolver");
}
}
public FlowDefinitionResource createFileResource(String path) {
Resource resource = new FileSystemResource(new File(path));
return new FlowDefinitionResource(getFlowId(resource), resource, null);
}
public FlowDefinitionResource createClassPathResource(String path, Class clazz) {
Resource resource = new ClassPathResource(path, clazz);
return new FlowDefinitionResource(getFlowId(resource), resource, null);
}
private String getFlowId(Resource flowResource) {
String fileName = flowResource.getFilename();
int extensionIndex = fileName.lastIndexOf('.');
if (extensionIndex != -1) {
return fileName.substring(0, extensionIndex);
} else {
return fileName;
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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.config;
import org.springframework.util.Assert;
/**
* A low-level definition of a attribute describing a flow artifact.
*
* @author Keith Donald
*/
class FlowElementAttribute {
/**
* The name of the attribute.
*/
private String name;
/**
* The value of the attribute before type-conversion.
*/
private String value;
/**
* The attribute type, optional, but necessary for type conversion.
*/
private String type;
public FlowElementAttribute(String name, String value, String type) {
Assert.hasText(name, "The name is required");
Assert.hasText(value, "The value is required");
this.name = name;
this.value = value;
this.type = type;
}
public String getName() {
return name;
}
public String getValue() {
return value;
}
public String getType() {
return type;
}
public boolean needsTypeConversion() {
return type != null && type.length() > 0;
}
}

View File

@@ -25,7 +25,6 @@ import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.util.xml.DomUtils;
import org.springframework.webflow.execution.factory.ConditionalFlowExecutionListenerLoader;
import org.w3c.dom.Element;
/**
@@ -33,22 +32,22 @@ import org.w3c.dom.Element;
*
* @author Ben Hale
*/
class ExecutionListenersBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
class FlowExecutionListenerLoaderBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
// elements and attributes
private static final String LISTENER_ELEMENT = "listener";
// properties
private static final String LISTENERS_PROPERTY = "listeners";
private static final String CRITERIA_ATTRIBUTE = "criteria";
private static final String REF_ATTRIBUTE = "ref";
// properties
private static final String LISTENERS_PROPERTY = "listeners";
protected Class getBeanClass(Element element) {
return ConditionalFlowExecutionListenerLoader.class;
return FlowExecutionListenerLoaderFactoryBean.class;
}
protected void doParse(Element element, BeanDefinitionBuilder definitionBuilder) {

View File

@@ -0,0 +1,68 @@
package org.springframework.webflow.config;
import java.util.Iterator;
import java.util.Map;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.webflow.execution.FlowExecutionListener;
import org.springframework.webflow.execution.factory.ConditionalFlowExecutionListenerLoader;
import org.springframework.webflow.execution.factory.FlowExecutionListenerCriteriaFactory;
import org.springframework.webflow.execution.factory.FlowExecutionListenerLoader;
/**
* A factory for a flow execution listener loader. Is a Spring FactoryBean, for provision by the flow execution listener
* loader bean definition parser. Is package-private, as people should not be using this class directly, but rather
* through the higher-level webflow-config Spring 2.x configuration namespace.
*
* @author Keith Donald
*/
class FlowExecutionListenerLoaderFactoryBean implements FactoryBean, InitializingBean {
/**
* The configured execution listeners and the criteria determining when they apply.
*/
private Map listenersWithCriteria;
/**
* The listener loader created by this factory. Is conditional, allowing listeners to apply to flow executions
* selectively based on some criteria expression.
*/
private ConditionalFlowExecutionListenerLoader listenerLoader;
/**
* A helper factory for converting string-encoded listener criteria to a FlowExecutionListenerCriteria object.
*/
private FlowExecutionListenerCriteriaFactory listenerCriteriaFactory = new FlowExecutionListenerCriteriaFactory();
/**
* Sets the listeners eligible for loading, and the criteria for when they should be loaded.
* @param listenersWithCriteria the listener-to-criteria map
*/
public void setListeners(Map listenersWithCriteria) {
this.listenersWithCriteria = listenersWithCriteria;
}
public void afterPropertiesSet() {
listenerLoader = new ConditionalFlowExecutionListenerLoader();
Iterator it = listenersWithCriteria.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
FlowExecutionListener listener = (FlowExecutionListener) entry.getKey();
String criteria = (String) entry.getValue();
listenerLoader.addListener(listener, listenerCriteriaFactory.getListenerCriteria(criteria));
}
}
public Object getObject() throws Exception {
return listenerLoader;
}
public Class getObjectType() {
return FlowExecutionListenerLoader.class;
}
public boolean isSingleton() {
return true;
}
}

View File

@@ -16,9 +16,6 @@
package org.springframework.webflow.config;
import org.springframework.core.enums.StaticLabeledEnum;
import org.springframework.webflow.execution.repository.continuation.ClientContinuationFlowExecutionRepository;
import org.springframework.webflow.execution.repository.continuation.ContinuationFlowExecutionRepository;
import org.springframework.webflow.execution.repository.support.SimpleFlowExecutionRepository;
/**
* Type-safe enumeration of logical flow execution repository types.
@@ -27,37 +24,32 @@ import org.springframework.webflow.execution.repository.support.SimpleFlowExecut
*
* @author Keith Donald
*/
public class RepositoryType extends StaticLabeledEnum {
public class FlowExecutionRepositoryType extends StaticLabeledEnum {
/**
* The 'simple' flow execution repository type.
* @see SimpleFlowExecutionRepository
*/
public static final RepositoryType SIMPLE = new RepositoryType(0, "Simple");
public static final FlowExecutionRepositoryType SIMPLE = new FlowExecutionRepositoryType(0, "Simple");
/**
* The 'continuation' flow execution repository type.
* @see ContinuationFlowExecutionRepository
*/
public static final RepositoryType CONTINUATION = new RepositoryType(1, "Continuation");
public static final FlowExecutionRepositoryType CONTINUATION = new FlowExecutionRepositoryType(1, "Continuation");
/**
* The 'client' (continuation) flow execution repository type.
* @see ClientContinuationFlowExecutionRepository
*/
public static final RepositoryType CLIENT = new RepositoryType(2, "Client");
public static final FlowExecutionRepositoryType CLIENT = new FlowExecutionRepositoryType(2, "Client");
/**
* The 'singleKey' flow execution repository type.
* @see SimpleFlowExecutionRepository
* @see SimpleFlowExecutionRepository#setAlwaysGenerateNewNextKey(boolean)
*/
public static final RepositoryType SINGLEKEY = new RepositoryType(3, "Single Key");
public static final FlowExecutionRepositoryType SINGLEKEY = new FlowExecutionRepositoryType(3, "Single Key");
/**
* Private constructor because this is a typesafe enum!
*/
private RepositoryType(int code, String label) {
private FlowExecutionRepositoryType(int code, String label) {
super(code, label);
}
}

View File

@@ -15,6 +15,11 @@
*/
package org.springframework.webflow.config;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
@@ -25,55 +30,60 @@ import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
/**
* {@link BeanDefinitionParser} for the <code>&lt;executor&gt;</code> tag.
* {@link BeanDefinitionParser} for the <code>&lt;flow-executor&gt;</code> tag.
*
* @author Ben Hale
* @author Christian Dupuis
* @author Keith Donald
*/
class ExecutorBeanDefinitionParser extends AbstractBeanDefinitionParser {
class FlowExecutorBeanDefinitionParser extends AbstractBeanDefinitionParser {
// elements and attributes
private static final String CONVERSATION_MANAGER_REF_ATTRIBUTE = "conversation-manager-ref";
private static final String CONVERSATION_MANAGER_REF_ATTRIBUTE = "conversation-manager";
private static final String EXECUTION_ATTRIBUTES_ELEMENT = "execution-attributes";
private static final String EXECUTION_ATTRIBUTES_ELEMENT = "flow-execution-attributes";
private static final String EXECUTION_LISTENERS_ELEMENT = "execution-listeners";
private static final String ALWAYS_REDIRECT_ON_PAUSE_ELEMENT = "alwaysRedirectOnPause";
private static final String ATTRIBUTE_ELEMENT = "attribute";
private static final String NAME_ATTRIBUTE = "name";
private static final String VALUE_ATTRIBUTE = "value";
private static final String TYPE_ATTRIBUTE = "type";
private static final String EXECUTION_LISTENERS_ELEMENT = "flow-execution-listeners";
private static final String MAX_CONTINUATIONS_ATTRIBUTE = "max-continuations";
private static final String MAX_CONVERSATIONS_ATTRIBUTE = "max-conversations";
private static final String REGISTRY_REF_ATTRIBUTE = "registry-ref";
private static final String REGISTRY_REF_ATTRIBUTE = "flow-registry";
private static final String REPOSITORY_ELEMENT = "repository";
private static final String REPOSITORY_TYPE_ATTRIBUTE = "repository-type";
private static final String TYPE_ATTRIBUTE = "type";
private static final String REPOSITORY_ELEMENT = "flow-execution-repository";
// properties
private static final String CONVERSATION_MANAGER_PROPERTY = "conversationManager";
private static final String DEFINITION_LOCATOR_PROPERTY = "definitionLocator";
private static final String DEFINITION_LOCATOR_PROPERTY = "flowDefinitionLocator";
private static final String EXECUTION_ATTRIBUTES_PROPERTY = "executionAttributes";
private static final String REPOSITORY_TYPE_PROPERTY = "flowExecutionRepositoryType";
private static final String EXECUTION_LISTENER_LOADER_PROPERTY = "executionListenerLoader";
private static final String EXECUTION_ATTRIBUTES_PROPERTY = "flowExecutionAttributes";
private static final String EXECUTION_LISTENER_LOADER_PROPERTY = "flowExecutionListenerLoader";
private static final String MAX_CONTINUATIONS_PROPERTY = "maxContinuations";
private static final String MAX_CONVERSATIONS_PROPERTY = "maxConversations";
private static final String REPOSITORY_TYPE_PROPERTY = "repositoryType";
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder
.rootBeanDefinition(FlowExecutorFactoryBean.class);
definitionBuilder.setSource(parserContext.extractSource(element));
definitionBuilder.addPropertyReference(DEFINITION_LOCATOR_PROPERTY, getRegistryRef(element, parserContext));
addExecutionAttributes(element, parserContext, definitionBuilder);
definitionBuilder.addPropertyValue(EXECUTION_ATTRIBUTES_PROPERTY, parseAttributes(element));
addExecutionListenerLoader(element, parserContext, definitionBuilder);
configureRepository(element, definitionBuilder, parserContext);
return definitionBuilder.getBeanDefinition();
@@ -88,18 +98,10 @@ class ExecutorBeanDefinitionParser extends AbstractBeanDefinitionParser {
private void configureRepository(Element element, BeanDefinitionBuilder definitionBuilder,
ParserContext parserContext) {
Element repositoryElement = DomUtils.getChildElementByTagName(element, REPOSITORY_ELEMENT);
String repositoryTypeAttribute = getRepositoryType(element);
if (repositoryElement != null) {
if (StringUtils.hasText(repositoryTypeAttribute)) {
parserContext.getReaderContext().error(
"The 'repositoryType' attribute of the 'executor' element must "
+ "not have a value if there is a 'repository' element", element);
}
definitionBuilder.addPropertyValue(REPOSITORY_TYPE_PROPERTY, getType(repositoryElement));
configureContinuations(repositoryElement, definitionBuilder, parserContext);
configureConversationManager(repositoryElement, definitionBuilder, parserContext);
} else if (StringUtils.hasText(repositoryTypeAttribute)) {
definitionBuilder.addPropertyValue(REPOSITORY_TYPE_PROPERTY, repositoryTypeAttribute);
}
}
@@ -155,20 +157,11 @@ class ExecutorBeanDefinitionParser extends AbstractBeanDefinitionParser {
String registryRef = element.getAttribute(REGISTRY_REF_ATTRIBUTE);
if (!StringUtils.hasText(registryRef)) {
parserContext.getReaderContext().error(
"The 'registry-ref' attribute of the 'executor' element must have a value", element);
"The 'registry-ref' attribute of the 'flow-executor' element must have a value", element);
}
return registryRef;
}
/**
* Returns the name of the repository type enum field detailed in the bean definition.
* @param element the element to extract the repository type from
* @return the type of the repository
*/
private String getRepositoryType(Element element) {
return element.getAttribute(REPOSITORY_TYPE_ATTRIBUTE).toUpperCase();
}
/**
* Returns the name of the repository type enum field detailed in the bean definition.
* @param element the element to extract the repository type from
@@ -205,18 +198,6 @@ class ExecutorBeanDefinitionParser extends AbstractBeanDefinitionParser {
return element.getAttribute(CONVERSATION_MANAGER_REF_ATTRIBUTE);
}
/**
* Parse execution attribute definitions contained in given element.
*/
private void addExecutionAttributes(Element element, ParserContext parserContext,
BeanDefinitionBuilder definitionBuilder) {
Element attributesElement = DomUtils.getChildElementByTagName(element, EXECUTION_ATTRIBUTES_ELEMENT);
if (attributesElement != null) {
definitionBuilder.addPropertyValue(EXECUTION_ATTRIBUTES_PROPERTY, parserContext.getDelegate()
.parseCustomElement(attributesElement, definitionBuilder.getBeanDefinition()));
}
}
/**
* Parse execution listener definitions contained in given element.
*/
@@ -228,4 +209,28 @@ class ExecutorBeanDefinitionParser extends AbstractBeanDefinitionParser {
.parseCustomElement(listenersElement, definitionBuilder.getBeanDefinition()));
}
}
private Set parseAttributes(Element element) {
Element executionAttributesElement = DomUtils.getChildElementByTagName(element, EXECUTION_ATTRIBUTES_ELEMENT);
if (executionAttributesElement != null) {
HashSet attributes = new HashSet();
Element redirectElement = DomUtils.getChildElementByTagName(executionAttributesElement,
ALWAYS_REDIRECT_ON_PAUSE_ELEMENT);
if (redirectElement != null) {
String value = redirectElement.getAttribute(VALUE_ATTRIBUTE);
attributes.add(new FlowElementAttribute("alwaysRedirectOnPause", value, "boolean"));
}
List attributeElements = DomUtils.getChildElementsByTagName(executionAttributesElement, ATTRIBUTE_ELEMENT);
for (Iterator it = attributeElements.iterator(); it.hasNext();) {
Element attributeElement = (Element) it.next();
String name = attributeElement.getAttribute(NAME_ATTRIBUTE);
String value = attributeElement.getAttribute(VALUE_ATTRIBUTE);
String type = attributeElement.getAttribute(TYPE_ATTRIBUTE);
attributes.add(new FlowElementAttribute(name, value, type));
}
return attributes;
} else {
return null;
}
}
}

View File

@@ -15,13 +15,15 @@
*/
package org.springframework.webflow.config;
import java.util.Map;
import java.util.Iterator;
import java.util.Set;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.binding.mapping.AttributeMapper;
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.convert.support.DefaultConversionService;
import org.springframework.util.Assert;
import org.springframework.webflow.context.ExternalContext;
import org.springframework.webflow.conversation.ConversationManager;
import org.springframework.webflow.conversation.impl.SessionBindingConversationManager;
import org.springframework.webflow.core.collection.AttributeMap;
@@ -31,16 +33,13 @@ import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
import org.springframework.webflow.engine.impl.FlowExecutionImplFactory;
import org.springframework.webflow.engine.impl.FlowExecutionImplStateRestorer;
import org.springframework.webflow.execution.FlowExecution;
import org.springframework.webflow.execution.FlowExecutionFactory;
import org.springframework.webflow.execution.FlowExecutionListener;
import org.springframework.webflow.execution.FlowExecutionKeyFactory;
import org.springframework.webflow.execution.factory.FlowExecutionListenerLoader;
import org.springframework.webflow.execution.factory.StaticFlowExecutionListenerLoader;
import org.springframework.webflow.execution.repository.FlowExecutionRepository;
import org.springframework.webflow.execution.repository.continuation.ClientContinuationFlowExecutionRepository;
import org.springframework.webflow.execution.repository.continuation.ContinuationFlowExecutionRepository;
import org.springframework.webflow.execution.repository.impl.ClientFlowExecutionRepository;
import org.springframework.webflow.execution.repository.impl.DefaultFlowExecutionRepository;
import org.springframework.webflow.execution.repository.support.FlowExecutionStateRestorer;
import org.springframework.webflow.execution.repository.support.SimpleFlowExecutionRepository;
import org.springframework.webflow.executor.FlowExecutor;
import org.springframework.webflow.executor.FlowExecutorImpl;
@@ -51,8 +50,7 @@ import org.springframework.webflow.executor.FlowExecutorImpl;
* This factory encapsulates the construction and assembly of a {@link FlowExecutor}, including the provision of its
* {@link FlowExecutionRepository} strategy.
* <p>
* The {@link #setDefinitionLocator(FlowDefinitionLocator) definition locator} property is required, all other
* properties are optional.
* The definition locator property is required, all other properties are optional.
* <p>
* This class has been designed with subclassing in mind. If you want to do advanced Spring Web Flow customization, e.g.
* using a custom {@link org.springframework.webflow.executor.FlowExecutor} implementation, consider subclassing this
@@ -61,22 +59,22 @@ import org.springframework.webflow.executor.FlowExecutorImpl;
* @author Keith Donald
* @author Erwin Vervaet
*/
public class FlowExecutorFactoryBean implements FactoryBean, InitializingBean {
class FlowExecutorFactoryBean implements FactoryBean, InitializingBean {
/**
* The locator the executor will use to access flow definitions registered in a central registry. Required.
*/
private FlowDefinitionLocator definitionLocator;
private FlowDefinitionLocator flowDefinitionLocator;
/**
* Execution attributes to apply.
*/
private MutableAttributeMap executionAttributes;
private Set flowExecutionAttributes;
/**
* The loader that will determine which listeners to attach to flow definition executions.
*/
private FlowExecutionListenerLoader executionListenerLoader;
private FlowExecutionListenerLoader flowExecutionListenerLoader;
/**
* The conversation manager to be used by the flow execution repository to store state associated with conversations
@@ -93,67 +91,40 @@ public class FlowExecutorFactoryBean implements FactoryBean, InitializingBean {
* The type of execution repository to configure with executors created by this factory. Optional. Will fallback to
* default value if not set.
*/
private RepositoryType repositoryType;
private FlowExecutionRepositoryType flowExecutionRepositoryType;
/**
* The maximum number of allowed continuations for a single conversation. Only used when the repository type is
* {@link RepositoryType#CONTINUATION}.
* {@link FlowExecutionRepositoryType#CONTINUATION}.
*/
private Integer maxContinuations;
/**
* A custom attribute mapper to use for mapping attributes of an {@link ExternalContext} to a new
* {@link FlowExecution} during the {@link FlowExecutor#launch(String, ExternalContext) launch flow} operation.
* The conversion service to use for type conversion of flow execution attribute values.
*/
private AttributeMapper inputMapper;
private ConversionService conversionService = new DefaultConversionService();
/**
* The flow executor this factory bean creates.
*/
private FlowExecutor flowExecutor;
/**
* Spring Web Flow executor system defaults.
*/
private FlowSystemDefaults defaults = new FlowSystemDefaults();
/**
* Sets the flow definition locator that will locate flow definitions needed for execution. Typically also a
* {@link FlowDefinitionRegistry}. Required.
* @param definitionLocator the flow definition locator (registry)
* @param flowDefinitionLocator the flow definition locator (registry)
*/
public void setDefinitionLocator(FlowDefinitionLocator definitionLocator) {
this.definitionLocator = definitionLocator;
public void setFlowDefinitionLocator(FlowDefinitionLocator flowDefinitionLocator) {
this.flowDefinitionLocator = flowDefinitionLocator;
}
/**
* Sets the system attributes that apply to flow executions launched by the executor created by this factory.
* Execution attributes may affect flow execution behavior.
* <p>
* Note: this method simply accepts a generic <code>java.util.Map</code> to allow for easy configuration by
* Spring. The map entries should consist of non-null String keys with object values.
* @param executionAttributes the flow execution system attributes
* @param flowExecutionAttributes the flow execution system attributes
*/
public void setExecutionAttributes(Map executionAttributes) {
this.executionAttributes = new LocalAttributeMap(executionAttributes);
}
/**
* Convenience setter that sets a single listener that always applies to flow executions launched by the executor
* created by this factory.
* @param executionListener the flow execution listener
*/
public void setExecutionListener(FlowExecutionListener executionListener) {
setExecutionListeners(new FlowExecutionListener[] { executionListener });
}
/**
* Convenience setter that sets a list of listeners that always apply to flow executions launched by the executor
* created by this factory.
* @param executionListeners the flow execution listeners
*/
public void setExecutionListeners(FlowExecutionListener[] executionListeners) {
setExecutionListenerLoader(new StaticFlowExecutionListenerLoader(executionListeners));
public void setFlowExecutionAttributes(Set flowExecutionAttributes) {
this.flowExecutionAttributes = flowExecutionAttributes;
}
/**
@@ -161,8 +132,8 @@ public class FlowExecutorFactoryBean implements FactoryBean, InitializingBean {
* control over what listeners should apply to executions of a flow definition launched by the executor created by
* this factory.
*/
public void setExecutionListenerLoader(FlowExecutionListenerLoader executionListenerLoader) {
this.executionListenerLoader = executionListenerLoader;
public void setFlowExecutionListenerLoader(FlowExecutionListenerLoader flowExecutionListenerLoader) {
this.flowExecutionListenerLoader = flowExecutionListenerLoader;
}
/**
@@ -171,28 +142,25 @@ public class FlowExecutorFactoryBean implements FactoryBean, InitializingBean {
* provided type.
* @param repositoryType the flow execution repository type
*/
public void setRepositoryType(RepositoryType repositoryType) {
this.repositoryType = repositoryType;
public void setFlowExecutionRepositoryType(FlowExecutionRepositoryType repositoryType) {
this.flowExecutionRepositoryType = repositoryType;
}
/**
* Set the maximum number of continuation snapshots allowed for a single conversation when using the
* {@link RepositoryType#CONTINUATION continuation} flow execution repository.
* @see ContinuationFlowExecutionRepository#setMaxContinuations(int)
* @since 1.0.1
*/
public void setMaxContinuations(int maxContinuations) {
this.maxContinuations = new Integer(maxContinuations);
}
/**
* Returns the configured maximum number of continuation snapshots allowed for a single conversation when using the
* {@link RepositoryType#CONTINUATION continuation} flow execution repository.
* @return the configured value or null if the user did not explicitly specify a value and wants to use the default
* @since 1.0.1
* Set the maximum number of allowed concurrent conversations in the session. This is a convenience setter to allow
* easy configuration of the maxConversations property of the default {@link SessionBindingConversationManager}. Do
* not use this when an explicit conversation manager is configured.
* @see SessionBindingConversationManager#setMaxConversations(int)
*/
protected Integer getMaxContinuations() {
return maxContinuations;
public void setMaxConversations(int maxConversations) {
this.maxConversations = new Integer(maxConversations);
}
/**
@@ -202,122 +170,67 @@ public class FlowExecutorFactoryBean implements FactoryBean, InitializingBean {
* The conversation manager is used by the flow execution repository subsystem to begin and end new conversations
* that store execution state.
* <p>
* By default, a {@link SessionBindingConversationManager} is used. Do not use {@link #setMaxConversations(int)}
* when using this method.
* By default, a {@link SessionBindingConversationManager} is used. Do not use setMaxConversations when using this
* method.
*/
public void setConversationManager(ConversationManager conversationManager) {
this.conversationManager = conversationManager;
}
/**
* Set the maximum number of allowed concurrent conversations in the session. This is a convenience setter to allow
* easy configuration of the maxConversations property of the default {@link SessionBindingConversationManager}. Do
* not use this when using {@link #setConversationManager(ConversationManager)}.
* @see SessionBindingConversationManager#setMaxConversations(int)
* @since 1.0.1
* Sets the conversion service for converting string-encoded flow execution attributes to typed values.
* @param conversionService the conversion service
*/
public void setMaxConversations(int maxConversations) {
this.maxConversations = new Integer(maxConversations);
}
/**
* Returns the configured maximum number of allowed concurrent conversations in the session. Will only be used when
* using the default conversation manager, e.g. when no explicit conversation manager has been configured using
* {@link #setConversationManager(ConversationManager)}.
* @return the configured value or null if the user did not explicitly specify a value and wants to use the default
* @since 1.0.1
*/
protected Integer getMaxConversations() {
return maxConversations;
}
/**
* Set the service responsible for mapping attributes of an {@link ExternalContext} to a new {@link FlowExecution}
* during the {@link FlowExecutor#launch(String, ExternalContext) launch flow} operation.
* <p>
* This is optional. If not set, a default implementation will be used that simply exposes all request parameters as
* flow execution input attributes.
*/
public void setInputMapper(AttributeMapper inputMapper) {
this.inputMapper = inputMapper;
}
/**
* Return the configured input mapper.
*/
protected AttributeMapper getInputMapper() {
return inputMapper;
}
/**
* Set system defaults that should be used.
* @param defaults the defaults to use.
*/
public void setDefaults(FlowSystemDefaults defaults) {
this.defaults = defaults;
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
// implementing InitializingBean
public void afterPropertiesSet() throws Exception {
Assert.notNull(definitionLocator, "The flow definition locator is required");
Assert.notNull(flowDefinitionLocator, "The flow definition locator property is required");
// apply defaults
executionAttributes = defaults.applyExecutionAttributes(executionAttributes);
repositoryType = defaults.applyIfNecessary(repositoryType);
FlowExecutorSystemDefaults defaults = new FlowExecutorSystemDefaults();
MutableAttributeMap executionAttributes = defaults.applyExecutionAttributes(createExecutionAttributeMap());
flowExecutionRepositoryType = defaults.applyIfNecessary(flowExecutionRepositoryType);
// pass all available parameters to the hook methods so that they
// can participate in the construction process
// a factory for flow executions
FlowExecutionFactory executionFactory = createFlowExecutionFactory(executionAttributes, executionListenerLoader);
// a strategy to restore deserialized flow executions
FlowExecutionStateRestorer executionStateRestorer = createFlowExecutionStateRestorer(definitionLocator,
executionAttributes, executionListenerLoader);
FlowExecutionStateRestorer executionStateRestorer = createFlowExecutionStateRestorer(flowDefinitionLocator,
executionAttributes, flowExecutionListenerLoader);
// a repository to store flow executions
FlowExecutionRepository executionRepository = createExecutionRepository(repositoryType, executionStateRestorer,
conversationManager);
FlowExecutionRepository executionRepository = createFlowExecutionRepository(flowExecutionRepositoryType,
executionStateRestorer, conversationManager);
// a factory for flow executions
FlowExecutionFactory executionFactory = createFlowExecutionFactory(executionAttributes,
flowExecutionListenerLoader, (FlowExecutionKeyFactory) executionRepository);
// combine all pieces of the puzzle to get an operational flow executor
flowExecutor = createFlowExecutor(definitionLocator, executionFactory, executionRepository);
flowExecutor = createFlowExecutor(flowDefinitionLocator, executionFactory, executionRepository);
}
// implementing FactoryBean
public Class getObjectType() {
return FlowExecutor.class;
}
public boolean isSingleton() {
return true;
}
public Object getObject() throws Exception {
return flowExecutor;
}
// subclassing hook methods
/**
* Create the conversation manager to be used in the default case, e.g. when no explicit conversation manager has
* been configured using {@link #setConversationManager(ConversationManager)}. This implementation return a
* {@link SessionBindingConversationManager}.
* @return the default conversation manager
*/
protected ConversationManager createDefaultConversationManager() {
SessionBindingConversationManager conversationManager = new SessionBindingConversationManager();
if (getMaxConversations() != null) {
conversationManager.setMaxConversations(getMaxConversations().intValue());
}
return conversationManager;
}
/**
* Create the flow execution factory to be used by the executor produced by this factory bean. Configure the
* execution factory appropriately. Subclasses may override if they which to use a custom execution factory, e.g. to
* use a custom FlowExecution implementation.
* @param executionAttributes execution attributes to apply to created executions
* @param executionListenerLoader decides which listeners to apply to created executions
* @return a new flow execution factory instance
*/
protected FlowExecutionFactory createFlowExecutionFactory(AttributeMap executionAttributes,
FlowExecutionListenerLoader executionListenerLoader) {
FlowExecutionImplFactory executionFactory = new FlowExecutionImplFactory();
executionFactory.setExecutionAttributes(executionAttributes);
if (executionListenerLoader != null) {
executionFactory.setExecutionListenerLoader(executionListenerLoader);
}
return executionFactory;
}
/**
* Create the flow execution state restorer to be used by the executor produced by this factory bean. Configure the
* state restorer appropriately. Subclasses may override if they which to use a custom state restorer
@@ -346,15 +259,15 @@ public class FlowExecutorFactoryBean implements FactoryBean, InitializingBean {
* default conversation manager should be used
* @return a new flow execution repository instance
*/
protected FlowExecutionRepository createExecutionRepository(RepositoryType repositoryType,
protected FlowExecutionRepository createFlowExecutionRepository(FlowExecutionRepositoryType repositoryType,
FlowExecutionStateRestorer executionStateRestorer, ConversationManager conversationManager) {
if (repositoryType == RepositoryType.CLIENT) {
if (repositoryType == FlowExecutionRepositoryType.CLIENT) {
if (conversationManager == null) {
// use the default no-op conversation manager
return new ClientContinuationFlowExecutionRepository(executionStateRestorer);
return new ClientFlowExecutionRepository(executionStateRestorer);
} else {
// use the conversation manager specified by the user
return new ClientContinuationFlowExecutionRepository(executionStateRestorer, conversationManager);
return new ClientFlowExecutionRepository(conversationManager, executionStateRestorer);
}
} else {
// determine the conversation manager to use
@@ -362,19 +275,21 @@ public class FlowExecutorFactoryBean implements FactoryBean, InitializingBean {
if (conversationManagerToUse == null) {
conversationManagerToUse = createDefaultConversationManager();
}
if (repositoryType == RepositoryType.SIMPLE) {
return new SimpleFlowExecutionRepository(executionStateRestorer, conversationManagerToUse);
} else if (repositoryType == RepositoryType.CONTINUATION) {
ContinuationFlowExecutionRepository repository = new ContinuationFlowExecutionRepository(
executionStateRestorer, conversationManagerToUse);
if (getMaxContinuations() != null) {
repository.setMaxContinuations(getMaxContinuations().intValue());
if (repositoryType == FlowExecutionRepositoryType.SIMPLE) {
DefaultFlowExecutionRepository repository = new DefaultFlowExecutionRepository(
conversationManagerToUse, executionStateRestorer);
repository.setMaxContinuations(1);
return repository;
} else if (repositoryType == FlowExecutionRepositoryType.CONTINUATION) {
DefaultFlowExecutionRepository repository = new DefaultFlowExecutionRepository(
conversationManagerToUse, executionStateRestorer);
if (maxContinuations != null) {
repository.setMaxContinuations(maxContinuations.intValue());
}
return repository;
} else if (repositoryType == RepositoryType.SINGLEKEY) {
SimpleFlowExecutionRepository repository = new SimpleFlowExecutionRepository(executionStateRestorer,
conversationManagerToUse);
} else if (repositoryType == FlowExecutionRepositoryType.SINGLEKEY) {
DefaultFlowExecutionRepository repository = new DefaultFlowExecutionRepository(
conversationManagerToUse, executionStateRestorer);
repository.setAlwaysGenerateNewNextKey(false);
return repository;
} else {
@@ -384,6 +299,38 @@ public class FlowExecutorFactoryBean implements FactoryBean, InitializingBean {
}
}
/**
* Create the conversation manager to be used in the default case, e.g. when no explicit conversation manager has
* been configured. This implementation return a {@link SessionBindingConversationManager}.
* @return the default conversation manager
*/
protected ConversationManager createDefaultConversationManager() {
SessionBindingConversationManager conversationManager = new SessionBindingConversationManager();
if (maxConversations != null) {
conversationManager.setMaxConversations(maxConversations.intValue());
}
return conversationManager;
}
/**
* Create the flow execution factory to be used by the executor produced by this factory bean. Configure the
* execution factory appropriately. Subclasses may override if they which to use a custom execution factory, e.g. to
* use a custom FlowExecution implementation.
* @param executionAttributes execution attributes to apply to created executions
* @param executionListenerLoader decides which listeners to apply to created executions
* @return a new flow execution factory instance
*/
protected FlowExecutionFactory createFlowExecutionFactory(AttributeMap executionAttributes,
FlowExecutionListenerLoader executionListenerLoader, FlowExecutionKeyFactory keyFactory) {
FlowExecutionImplFactory executionFactory = new FlowExecutionImplFactory();
executionFactory.setExecutionAttributes(executionAttributes);
if (executionListenerLoader != null) {
executionFactory.setExecutionListenerLoader(executionListenerLoader);
}
executionFactory.setExecutionKeyFactory(keyFactory);
return executionFactory;
}
/**
* Create the flow executor instance created by this factory bean and configure it appropriately. Subclasses may
* override if they which to use a custom executor implementation.
@@ -394,32 +341,27 @@ public class FlowExecutorFactoryBean implements FactoryBean, InitializingBean {
*/
protected FlowExecutor createFlowExecutor(FlowDefinitionLocator definitionLocator,
FlowExecutionFactory executionFactory, FlowExecutionRepository executionRepository) {
FlowExecutorImpl flowExecutor = new FlowExecutorImpl(definitionLocator, executionFactory, executionRepository);
if (getInputMapper() != null) {
flowExecutor.setInputMapper(inputMapper);
return new FlowExecutorImpl(definitionLocator, executionFactory, executionRepository);
}
private MutableAttributeMap createExecutionAttributeMap() {
LocalAttributeMap executionAttributes = new LocalAttributeMap();
if (flowExecutionAttributes != null) {
for (Iterator it = flowExecutionAttributes.iterator(); it.hasNext();) {
FlowElementAttribute attribute = (FlowElementAttribute) it.next();
executionAttributes.put(attribute.getName(), getConvertedValue(attribute));
}
}
return flowExecutor;
return executionAttributes;
}
// implementing FactoryBean
public Class getObjectType() {
return FlowExecutor.class;
}
public boolean isSingleton() {
return true;
}
public Object getObject() throws Exception {
return getFlowExecutor();
}
/**
* Returns the flow executor constructed by the factory bean.
* @since 1.0.2
*/
public FlowExecutor getFlowExecutor() {
return flowExecutor;
private Object getConvertedValue(FlowElementAttribute attribute) {
if (attribute.needsTypeConversion()) {
ConversionExecutor converter = conversionService.getConversionExecutorByTargetAlias(String.class, attribute
.getType());
return converter.execute(attribute.getValue());
} else {
return attribute.getValue();
}
}
}

View File

@@ -20,7 +20,6 @@ import java.io.Serializable;
import org.springframework.core.style.ToStringCreator;
import org.springframework.webflow.core.collection.LocalAttributeMap;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.engine.support.ApplicationViewSelector;
/**
* Encapsulates overall flow system configuration defaults. Allows for centralized application of, and if necessary,
@@ -28,7 +27,7 @@ import org.springframework.webflow.engine.support.ApplicationViewSelector;
*
* @author Keith Donald
*/
public class FlowSystemDefaults implements Serializable {
class FlowExecutorSystemDefaults implements Serializable {
/**
* The default 'alwaysRedirectOnPause' execution attribute value.
@@ -38,12 +37,11 @@ public class FlowSystemDefaults implements Serializable {
/**
* The default flow execution repository type.
*/
private RepositoryType repositoryType = RepositoryType.CONTINUATION;
private FlowExecutionRepositoryType repositoryType = FlowExecutionRepositoryType.CONTINUATION;
/**
* Overrides the alwaysRedirectOnPause execution attribute default. Defaults to "true".
* @param alwaysRedirectOnPause the new default value
* @see ApplicationViewSelector#ALWAYS_REDIRECT_ON_PAUSE_ATTRIBUTE
*/
public void setAlwaysRedirectOnPause(boolean alwaysRedirectOnPause) {
this.alwaysRedirectOnPause = alwaysRedirectOnPause;
@@ -53,7 +51,7 @@ public class FlowSystemDefaults implements Serializable {
* Overrides the default repository type.
* @param repositoryType the new default value
*/
public void setRepositoryType(RepositoryType repositoryType) {
public void setRepositoryType(FlowExecutionRepositoryType repositoryType) {
this.repositoryType = repositoryType;
}
@@ -67,9 +65,8 @@ public class FlowSystemDefaults implements Serializable {
if (executionAttributes == null) {
executionAttributes = new LocalAttributeMap(1, 1);
}
if (!executionAttributes.contains(ApplicationViewSelector.ALWAYS_REDIRECT_ON_PAUSE_ATTRIBUTE)) {
executionAttributes.put(ApplicationViewSelector.ALWAYS_REDIRECT_ON_PAUSE_ATTRIBUTE, new Boolean(
alwaysRedirectOnPause));
if (!executionAttributes.contains("alwaysRedirectOnPause")) {
executionAttributes.put("alwaysRedirectOnPause", new Boolean(alwaysRedirectOnPause));
}
return executionAttributes;
}
@@ -79,7 +76,7 @@ public class FlowSystemDefaults implements Serializable {
* @param selectedType the selected repository type (may be null if no selection was made)
* @return the repository type, with the default applied if necessary
*/
public RepositoryType applyIfNecessary(RepositoryType selectedType) {
public FlowExecutionRepositoryType applyIfNecessary(FlowExecutionRepositoryType selectedType) {
if (selectedType == null) {
return repositoryType;
} else {

View File

@@ -0,0 +1,49 @@
package org.springframework.webflow.config;
import java.util.Collections;
import java.util.Set;
import org.springframework.util.Assert;
/**
* A low-level pointer to a flow definition that will be registered in a registry and built from an external file
* resource.
*
* @author Keith Donald
*/
class FlowLocation {
/**
* The id to assign to the flow definition.
*/
private String id;
/**
* The string-encoded path to the flow definition file resource.
*/
private String path;
/**
* Attributes to assign to the flow definition.
*/
private Set attributes;
public FlowLocation(String id, String path, Set attributes) {
Assert.hasText(path, "The path is required");
this.id = id;
this.path = path;
this.attributes = (attributes != null ? attributes : Collections.EMPTY_SET);
}
public String getId() {
return id;
}
public String getPath() {
return path;
}
public Set getAttributes() {
return attributes;
}
}

View File

@@ -0,0 +1,131 @@
/*
* 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.config;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
/**
* {@link BeanDefinitionParser} for the flow <code>&lt;registry&gt;</code> tag.
*
* @author Keith Donald
*/
class FlowRegistryBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
private static final String FLOW_BUILDER_SERVICES_ATTRIBUTE = "flow-builder-services";
private static final String FLOW_LOCATION_ELEMENT = "flow-location";
private static final String FLOW_BUILDER_ELEMENT = "flow-builder";
private static final String ID_ATTRIBUTE = "id";
private static final String PATH_ATTRIBUTE = "path";
private static final String CLASS_ATTRIBUTE = "class";
private static final String DEFINITION_ATTRIBUTES_ELEMENT = "flow-definition-attributes";
private static final String ATTRIBUTE_ELEMENT = "attribute";
private static final String NAME_ATTRIBUTE = "name";
private static final String VALUE_ATTRIBUTE = "value";
private static final String TYPE_ATTRIBUTE = "type";
private static final String FLOW_LOCATIONS_PROPERTY = "flowLocations";
private static final String FLOW_BUILDERS_PROPERTY = "flowBuilders";
private static final String FLOW_BUILDER_SERVICES_PROPERTY = "flowBuilderServices";
protected Class getBeanClass(Element element) {
return FlowRegistryFactoryBean.class;
}
protected void doParse(Element element, BeanDefinitionBuilder definitionBuilder) {
String flowBuilderServices = getFlowBuilderServicesAttribute(element);
if (StringUtils.hasText(flowBuilderServices)) {
definitionBuilder.addPropertyReference(FLOW_BUILDER_SERVICES_PROPERTY, flowBuilderServices);
}
definitionBuilder.addPropertyValue(FLOW_LOCATIONS_PROPERTY, parseLocations(element));
definitionBuilder.addPropertyValue(FLOW_BUILDERS_PROPERTY, parseFlowBuilders(element));
}
private List parseLocations(Element element) {
List locationElements = DomUtils.getChildElementsByTagName(element, FLOW_LOCATION_ELEMENT);
if (locationElements.isEmpty()) {
return Collections.EMPTY_LIST;
}
List locations = new ArrayList(locationElements.size());
for (Iterator it = locationElements.iterator(); it.hasNext();) {
Element locationElement = (Element) it.next();
String id = locationElement.getAttribute(ID_ATTRIBUTE);
String path = locationElement.getAttribute(PATH_ATTRIBUTE);
locations.add(new FlowLocation(id, path, parseAttributes(locationElement)));
}
return locations;
}
private Set parseAttributes(Element element) {
Element definitionAttributesElement = DomUtils.getChildElementByTagName(element, DEFINITION_ATTRIBUTES_ELEMENT);
if (definitionAttributesElement != null) {
List attributeElements = DomUtils.getChildElementsByTagName(definitionAttributesElement, ATTRIBUTE_ELEMENT);
HashSet attributes = new HashSet(attributeElements.size());
for (Iterator it = attributeElements.iterator(); it.hasNext();) {
Element attributeElement = (Element) it.next();
String name = attributeElement.getAttribute(NAME_ATTRIBUTE);
String value = attributeElement.getAttribute(VALUE_ATTRIBUTE);
String type = attributeElement.getAttribute(TYPE_ATTRIBUTE);
attributes.add(new FlowElementAttribute(name, value, type));
}
return attributes;
} else {
return null;
}
}
private List parseFlowBuilders(Element element) {
List builderElements = DomUtils.getChildElementsByTagName(element, FLOW_BUILDER_ELEMENT);
if (builderElements.isEmpty()) {
return Collections.EMPTY_LIST;
}
List builders = new ArrayList(builderElements.size());
for (Iterator it = builderElements.iterator(); it.hasNext();) {
Element builderElement = (Element) it.next();
String id = builderElement.getAttribute(ID_ATTRIBUTE);
String className = builderElement.getAttribute(CLASS_ATTRIBUTE);
builders.add(new FlowBuilderInfo(id, className, parseAttributes(builderElement)));
}
return builders;
}
private String getFlowBuilderServicesAttribute(Element element) {
return element.getAttribute(FLOW_BUILDER_SERVICES_ATTRIBUTE);
}
}

View File

@@ -0,0 +1,239 @@
package org.springframework.webflow.config;
import java.util.Iterator;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.ClassUtils;
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.definition.FlowDefinition;
import org.springframework.webflow.definition.registry.FlowDefinitionConstructionException;
import org.springframework.webflow.definition.registry.FlowDefinitionHolder;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistryImpl;
import org.springframework.webflow.engine.builder.FlowAssembler;
import org.springframework.webflow.engine.builder.FlowBuilder;
import org.springframework.webflow.engine.builder.FlowBuilderContext;
import org.springframework.webflow.engine.builder.RefreshableFlowDefinitionHolder;
import org.springframework.webflow.engine.builder.support.FlowBuilderContextImpl;
import org.springframework.webflow.engine.builder.support.FlowBuilderServices;
import org.springframework.webflow.engine.builder.xml.XmlFlowBuilder;
/**
* A factory for a flow definition registry. Is a Spring FactoryBean, for provision by the flow definition registry bean
* definition parser. Is package-private, as people should not be using this class directly, but rather through the
* higher-level webflow-config Spring 2.x configuration namespace.
*
* @author Keith Donald
*/
class FlowRegistryFactoryBean implements FactoryBean, ResourceLoaderAware, BeanFactoryAware, InitializingBean {
/**
* The definition registry produced by this factory bean.
*/
private FlowDefinitionRegistry flowRegistry;
/**
* Flow definitions defined in external files that should be registered in the registry produced by this factory
* bean.
*/
private FlowLocation[] flowLocations;
/**
* Java {@link FlowBuilder flow builder} classes that should be registered in the registry produced by this factory
* bean.
*/
private FlowBuilderInfo[] flowBuilders;
/**
* The holder for services needed to build flow definitions registered in this registry.
*/
private FlowBuilderServices flowBuilderServices;
/**
* A helper for creating abstract representation of externalized flow definition resources.
*/
private FlowDefinitionResourceFactory flowResourceFactory;
/**
* The container's resource loader.
*/
private ResourceLoader resourceLoader;
/**
* The containing bean factory this factory bean was deployed in.
*/
private BeanFactory beanFactory;
public void setFlowLocations(FlowLocation[] flowLocations) {
this.flowLocations = flowLocations;
}
public void setFlowBuilders(FlowBuilderInfo[] flowBuilders) {
this.flowBuilders = flowBuilders;
}
public void setFlowBuilderServices(FlowBuilderServices flowBuilderServices) {
this.flowBuilderServices = flowBuilderServices;
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
public void afterPropertiesSet() throws Exception {
if (flowBuilderServices == null) {
initFlowBuilderServices();
}
flowResourceFactory = new FlowDefinitionResourceFactory(resourceLoader);
flowRegistry = new FlowDefinitionRegistryImpl();
registerFlowLocations();
registerFlowBuilders();
}
public Object getObject() throws Exception {
return flowRegistry;
}
public Class getObjectType() {
return FlowDefinitionRegistry.class;
}
public boolean isSingleton() {
return true;
}
private void registerFlowLocations() {
if (flowLocations != null) {
for (int i = 0; i < flowLocations.length; i++) {
FlowLocation location = flowLocations[i];
flowRegistry.registerFlowDefinition(createFlowDefinitionHolder(location));
}
}
}
private void registerFlowBuilders() {
if (flowBuilders != null) {
for (int i = 0; i < flowBuilders.length; i++) {
FlowBuilderInfo builder = flowBuilders[i];
flowRegistry.registerFlowDefinition(createFlowDefinitionHolder(builder));
}
}
}
private FlowDefinitionHolder createFlowDefinitionHolder(FlowLocation location) {
FlowDefinitionResource flowResource = createResource(location);
FlowBuilder builder = createFlowBuilder(flowResource);
FlowBuilderContext builderContext = new FlowBuilderContextImpl(flowResource.getId(), flowResource
.getAttributes(), flowRegistry, flowBuilderServices);
FlowAssembler assembler = new FlowAssembler(builder, builderContext);
return new RefreshableFlowDefinitionHolder(assembler);
}
private FlowDefinitionResource createResource(FlowLocation location) {
AttributeMap flowAttributes = getFlowAttributes(location.getAttributes());
return flowResourceFactory.createResource(location.getPath(), flowAttributes, location.getId());
}
private AttributeMap getFlowAttributes(Set attributes) {
MutableAttributeMap flowAttributes = null;
if (!attributes.isEmpty()) {
flowAttributes = new LocalAttributeMap();
for (Iterator it = attributes.iterator(); it.hasNext();) {
FlowElementAttribute attribute = (FlowElementAttribute) it.next();
flowAttributes.put(attribute.getName(), getConvertedValue(attribute));
}
}
return flowAttributes;
}
private FlowBuilder createFlowBuilder(FlowDefinitionResource resource) {
if (isXml(resource.getPath())) {
return new XmlFlowBuilder(resource.getPath());
} else {
throw new IllegalArgumentException(resource
+ " is not a supported resource type; supported types are [.xml]");
}
}
private boolean isXml(Resource flowResource) {
return flowResource.getFilename().endsWith(".xml");
}
private Object getConvertedValue(FlowElementAttribute attribute) {
if (attribute.needsTypeConversion()) {
ConversionExecutor converter = flowBuilderServices.getConversionService()
.getConversionExecutorByTargetAlias(String.class, attribute.getType());
return converter.execute(attribute.getValue());
} else {
return attribute.getValue();
}
}
private FlowDefinitionHolder createFlowDefinitionHolder(FlowBuilderInfo builder) {
ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
AttributeMap flowAttributes = getFlowAttributes(builder.getAttributes());
FlowBuilderContext builderContext = new FlowBuilderContextImpl(builder.getId(), flowAttributes, flowRegistry,
flowBuilderServices);
return new FlowBuilderCreatingFlowDefinitionHolder(builder.getClassName(), classLoader, builderContext);
}
private void initFlowBuilderServices() {
flowBuilderServices = new FlowBuilderServices();
flowBuilderServices.setResourceLoader(resourceLoader);
flowBuilderServices.setBeanFactory(beanFactory);
}
class FlowBuilderCreatingFlowDefinitionHolder implements FlowDefinitionHolder {
private String flowBuilderClassName;
private ClassLoader classLoader;
private FlowBuilderContext builderContext;
public FlowBuilderCreatingFlowDefinitionHolder(String flowBuilderClassName, ClassLoader classLoader,
FlowBuilderContext builderContext) {
this.flowBuilderClassName = flowBuilderClassName;
this.classLoader = classLoader;
this.builderContext = builderContext;
}
public String getFlowDefinitionId() {
return builderContext.getFlowId();
}
public FlowDefinition getFlowDefinition() throws FlowDefinitionConstructionException {
try {
Class flowBuilderClass = classLoader.loadClass(flowBuilderClassName);
FlowBuilder builder = (FlowBuilder) flowBuilderClass.newInstance();
FlowAssembler assembler = new FlowAssembler(builder, builderContext);
return assembler.assembleFlow();
} catch (ClassNotFoundException e) {
throw new FlowDefinitionConstructionException(getFlowDefinitionId(), e);
} catch (InstantiationException e) {
throw new FlowDefinitionConstructionException(getFlowDefinitionId(), e);
} catch (IllegalAccessException e) {
throw new FlowDefinitionConstructionException(getFlowDefinitionId(), e);
}
}
public void refresh() throws FlowDefinitionConstructionException {
}
}
}

View File

@@ -1,113 +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.config;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.util.xml.DomUtils;
import org.springframework.webflow.definition.registry.FlowDefinitionResource;
import org.springframework.webflow.engine.builder.FlowRegistryFactoryBean;
import org.w3c.dom.Element;
/**
* {@link BeanDefinitionParser} for the <code>&lt;registry&gt;</code> tag.
*
* @author Ben Hale
*/
class RegistryBeanDefinitionParser extends AbstractBeanDefinitionParser {
// elements
private static final String LOCATION_ELEMENT = "location";
private static final String NAMESPACE_ELEMENT = "namespace";
// attributes
private static final String ID_ATTRIBUTE = "id";
private static final String NAME_ATTRIBUTE = "name";
private static final String PATH_ATTRIBUTE = "path";
// Properties
private static final String XML_NAMESPACE_FLOW_MAPPINGS_PROPERTY = "xmlNamespaceFlowMappings";
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder
.rootBeanDefinition(FlowRegistryFactoryBean.class);
definitionBuilder.setSource(parserContext.extractSource(element));
parseXml(element, definitionBuilder, parserContext);
return definitionBuilder.getBeanDefinition();
}
public void parseXml(Element element, BeanDefinitionBuilder definitionBuilder, ParserContext parserContext) {
Map xmlNamespaceFlowMappings = new HashMap();
xmlNamespaceFlowMappings.put("", parseXmlElements(
DomUtils.getChildElementsByTagName(element, LOCATION_ELEMENT), parserContext));
List namespaceElements = DomUtils.getChildElementsByTagName(element, NAMESPACE_ELEMENT);
for (Iterator it = namespaceElements.iterator(); it.hasNext();) {
Element namespaceElement = (Element) it.next();
String namespace = namespaceElement.getAttribute(NAME_ATTRIBUTE);
xmlNamespaceFlowMappings.put(namespace, parseXmlElements(DomUtils.getChildElementsByTagName(
namespaceElement, LOCATION_ELEMENT), parserContext));
}
definitionBuilder.addPropertyValue(XML_NAMESPACE_FLOW_MAPPINGS_PROPERTY, xmlNamespaceFlowMappings);
}
private Set parseXmlElements(List locationElements, ParserContext parserContext) {
ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(parserContext
.getReaderContext().getResourceLoader());
Set resources = new HashSet();
for (Iterator it = locationElements.iterator(); it.hasNext();) {
Element locationElement = (Element) it.next();
try {
Resource[] locations = resolver.getResources(locationElement.getAttribute(PATH_ATTRIBUTE));
if (locationElement.hasAttribute(ID_ATTRIBUTE)) {
if (locations.length != 1) {
parserContext.getReaderContext().error(
"The 'path' attribute of the 'location' element must point to a single value "
+ "flow definition if an id has been specified", locationElement);
} else {
resources.add(new FlowDefinitionResource(locationElement.getAttribute(ID_ATTRIBUTE),
locations[0]));
}
} else {
for (int i = 0; i < locations.length; i++) {
resources.add(new FlowDefinitionResource(locations[i]));
}
}
} catch (IOException e) {
parserContext.getReaderContext().error(
"The 'path' attribute of the 'location' element must point to a valid flow definition "
+ "or definitions", locationElement);
}
}
return resources;
}
}

View File

@@ -15,51 +15,18 @@
*/
package org.springframework.webflow.config;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
/**
* <code>NamespaceHandler</code> for the <code>webflow-config</code> namespace.
* <p>
* Provides {@link BeanDefinitionParser bean definition parsers} for the <code>&lt;executor&gt;</code> and
* <code>&lt;registry&gt;</code> tags. An <code>executor</code> tag can include an <code>execution-listeners</code>
* tag and a <code>registry</code> tag can include <code>location</code> tags.
* <p>
* Using the <code>executor</code> tag you can configure a {@link FlowExecutorFactoryBean} that creates a
* {@link org.springframework.webflow.executor.FlowExecutor}. The <code>executor</code> tag allows you to specify the
* repository type and a reference to a registry.
*
* <pre class="code">
* &lt;flow:executor id=&quot;registry&quot; registry-ref=&quot;registry&quot; repository-type=&quot;continuation&quot; &gt;
* &lt;flow:execution-listeners&gt;
* &lt;flow:listener ref=&quot;listener1&quot; /&gt;
* &lt;flow:listener ref=&quot;listener2&quot; ref=&quot;*&quot; /&gt;
* &lt;flow:listener ref=&quot;listener3&quot; ref=&quot;flow1, flow2, flow3&quot; /&gt;
* &lt;flow:execution-listeners /&gt;
* &lt;/flow:executor&gt;
* </pre>
*
* <p>
* Using the <code>registry</code> tag you can configure an
* {@link org.springframework.webflow.engine.builder.xml.XmlFlowRegistryFactoryBean} to create a registry for use by any
* number of <code>executor</code>s. The <code>registry</code> tag supports in-line flow definition locations.
*
* <pre class="code">
* &lt;flow:registry id=&quot;registry&quot;&gt;
* &lt;flow:location path=&quot;/path/to/flow.xml&quot; /&gt;
* &lt;flow:location path=&quot;/path/with/wildcards/*-flow.xml&quot; /&gt;
* &lt;/flow:registry&gt;
* </pre>
*
* @author Keith Donald
* @author Ben Hale
*/
public class WebFlowConfigNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("execution-attributes", new ExecutionAttributesBeanDefinitionParser());
registerBeanDefinitionParser("execution-listeners", new ExecutionListenersBeanDefinitionParser());
registerBeanDefinitionParser("executor", new ExecutorBeanDefinitionParser());
registerBeanDefinitionParser("registry", new RegistryBeanDefinitionParser());
registerBeanDefinitionParser("enable-scopes", new EnableScopesBeanDefinitionParser());
registerBeanDefinitionParser("flow-executor", new FlowExecutorBeanDefinitionParser());
registerBeanDefinitionParser("flow-execution-listeners", new FlowExecutionListenerLoaderBeanDefinitionParser());
registerBeanDefinitionParser("flow-registry", new FlowRegistryBeanDefinitionParser());
}
}

View File

@@ -1,112 +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.config.scope;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.execution.FlowExecutionContext;
import org.springframework.webflow.execution.FlowExecutionContextHolder;
import org.springframework.webflow.execution.FlowSession;
/**
* Base class for {@link Scope} implementations that access a Web Flow scope from the current thread-bound
* {@link FlowExecutionContext} object.
* <p>
* Subclasses simply need to implement {@link #getScope()} to return the {@link MutableAttributeMap scope map} to
* access.
* <p>
* Relies on a thread-bound
* @{link FlowExecutionContext} instance located through the
* @{link FlowExecutionContextHolder}.
*
* @see FlowExecutionContext
* @see FlowExecutionContextHolder
*
* @author Ben Hale
*/
public abstract class AbstractWebFlowScope implements Scope {
/**
* Logger, usable by subclasses.
*/
protected final Log logger = LogFactory.getLog(getClass());
public Object get(String name, ObjectFactory objectFactory) {
MutableAttributeMap scope = getScope();
Object scopedObject = scope.get(name);
if (scopedObject == null) {
if (logger.isDebugEnabled()) {
logger.debug("No scoped instance '" + name + "' found; creating new instance");
}
scopedObject = objectFactory.getObject();
scope.put(name, scopedObject);
} else {
if (logger.isDebugEnabled()) {
logger.debug("Returning scoped instance '" + name + "'");
}
}
return scopedObject;
}
public Object remove(String name) {
return getScope().remove(name);
}
/**
* Template method that returns the target scope map.
* @return the target scope map
* @see FlowExecutionContext#getConversationScope()
* @see FlowExecutionContext#getActiveSession()
* @see FlowSession#getFlashMap()
* @see FlowSession#getScope()
* @throws IllegalStateException if the scope could not be accessed
*/
protected abstract MutableAttributeMap getScope() throws IllegalStateException;
/**
* Always returns <code>null</code> as most Spring Web Flow scopes do not have obvious conversation ids.
* Subclasses should override this method where conversation ids can be intelligently returned.
* @return always returns <code>null</code>
*/
public String getConversationId() {
return null;
}
/**
* Will not register a destruction callback as Spring Web Flow does not support destruction of scoped beans.
* Subclasses should override this method where where destruction can adequately be accomplished.
* @param name the name of the bean to register the callback for
* @param callback the callback to execute
*/
public void registerDestructionCallback(String name, Runnable callback) {
logger.warn("Destruction callback for '" + name + "' was not registered. Spring Web Flow does not "
+ "support destruction of scoped beans.");
}
/**
* Returns the current flow execution context. Used by subclasses to easily get access to the thread-bound flow
* execution context.
* @return the current thread-bound flow execution context
* @throws IllegalStateException if the current flow execution context is not bound
*/
protected FlowExecutionContext getFlowExecutionContext() throws IllegalStateException {
return FlowExecutionContextHolder.getFlowExecutionContext();
}
}

View File

@@ -1,33 +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.config.scope;
import org.springframework.beans.factory.config.Scope;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.execution.FlowExecution;
/**
* Conversation {@link Scope scope} implementation.
*
* @see FlowExecution#getConversationScope()
*
* @author Ben Hale
*/
public class ConversationScope extends AbstractWebFlowScope {
protected MutableAttributeMap getScope() {
return getFlowExecutionContext().getConversationScope();
}
}

View File

@@ -1,33 +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.config.scope;
import org.springframework.beans.factory.config.Scope;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.execution.FlowExecutionContext;
/**
* Flash {@link Scope scope} implementation.
*
* @see FlowExecutionContext#getFlashScope()
*
* @author Ben Hale
*/
public class FlashScope extends AbstractWebFlowScope {
protected MutableAttributeMap getScope() {
return getFlowExecutionContext().getFlashScope();
}
}

View File

@@ -1,33 +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.config.scope;
import org.springframework.beans.factory.config.Scope;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.execution.FlowSession;
/**
* Flow {@link Scope scope} implementation.
*
* @see FlowSession#getScope()
*
* @author Ben Hale
*/
public class FlowScope extends AbstractWebFlowScope {
protected MutableAttributeMap getScope() {
return getFlowExecutionContext().getActiveSession().getScope();
}
}

View File

@@ -1,44 +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.config.scope;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.core.Ordered;
import org.springframework.webflow.execution.ScopeType;
/**
* Registers the Spring Web Flow bean scopes with a
* @{link ConfigurableListableBeanFactory}.
*
* @author Ben Hale
* @see Scope
*/
public class ScopeRegistrar implements BeanFactoryPostProcessor, Ordered {
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.registerScope(ScopeType.FLASH.getLabel().toLowerCase(), new FlashScope());
beanFactory.registerScope(ScopeType.FLOW.getLabel().toLowerCase(), new FlowScope());
beanFactory.registerScope(ScopeType.CONVERSATION.getLabel().toLowerCase(), new ConversationScope());
}
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}

View File

@@ -1,6 +0,0 @@
<html>
<body>
Support code to allow access to the Spring Web Flow scopes (conversation, flow, flash)
from a Spring ApplicationContext.
</body>
</html>

View File

@@ -1,345 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema
xmlns="http://www.springframework.org/schema/webflow-config"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://www.springframework.org/schema/webflow-config"
elementFormDefault="qualified" attributeFormDefault="unqualified"
version="1.0.1">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Spring Web Flow Configuration Schema
Authors: Keith Donald, Ben Hale
<br>
Provides an easy way to configure a flow executor and an XML flow definition registry.
]]>
</xsd:documentation>
</xsd:annotation>
<xsd:import namespace="http://www.springframework.org/schema/beans"
schemaLocation="http://www.springframework.org/schema/beans/spring-beans-2.0.xsd" />
<xsd:element name="registry">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Deploys a flow definition registy. A flow definition registry contains a set of flow definitions.
A flow definition is typically built from externalized resource location such as a .xml file.
<br>
Each flow definition registered in this registry is assigned a unique identifier. By default,
this identifier is the name of the externalized resource minus its file extension. For example,
a registry containing flow definitions built from the files "orderitem-flow.xml" and "shipping-flow.xml"
would index those definitions by "orderitem-flow" and "shipping-flow" by default.
<br>
A flow registry is used by a flow executor at runtime to launch new executions of flow definitions.
]]>
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:sequence>
<xsd:element name="location" type="locationType" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Specifies a path to a externalized flow definition resource. The flow definition built from this
resource will be registered in this registry.
<br>
Individual paths such as:
<pre>
/WEB-INF/flows/orderitem-flow.xml
</pre>
... are supported as well as wildcard paths such as:
<pre>
/WEB-INF/flows/**/*-flow.xml
</pre>
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="locationType">
<xsd:attribute name="path" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The path to the externalized flow definition resource. May be a path to a single resource or
a ANT-style path expression that matches multiple resources.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:element name="executor">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Deploys a flow executor. A flow executor executes flow definitions. More specifically, an executor
is responsible for launching new flow executions as well as resuming paused flow executions.
A flow execution that spans more than one request is said to have 'paused' and is stored in
an execution repository.
]]>
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:sequence>
<xsd:element name="repository" type="repositoryType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Explicit repository configuration for this executor. This element is used if configuration needs
to be more fine grained than the repositoryType attribute on executor.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="execution-attributes" type="execution-attributesType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Execution attributes to associate with new flow executions launched by this executor.
These attributes may influence execution behavior.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="execution-listeners" type="execution-listenersType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The listeners eligible for observing the lifecycle of executions launched by this executor.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="repository-type" type="repositoryTypeAttribute">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The type of execution repository to use. The repository is responsible for managing flow execution
persistence between requests.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="registry-ref" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The idref to the registry this executor will use to locate flow definitions for execution.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:simpleType name="repositoryTypeAttribute">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="continuation">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The continuation repository type. Use this to snapshot flow execution state server-side to support use
of the browser back button.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="simple">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The simple repository type. Use of this strategy incurs minimal storage overhead but explicitly prevents
resubmits using the browser back button.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="client">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The client continuation repository type. Use this to snapshot flow execution state client-side to support use of the browser
back button in a stateless server environment.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="singlekey">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The "single key" repository type. Use of this strategy assigns a single key per flow execution that remains
constant throughout the life of the execution.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="repositoryType">
<xsd:attribute name="type" type="repositoryTypeAttribute" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The type of flow execution repository to use. The repository is responsible for managing flow execution
persistence between requests.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="max-conversations" type="xsd:integer">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The maximum number of concurrent conversations allowed by this repository. It is illegal to set this
attribute if you are also setting the 'conversation-manager-ref' attribute.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="max-continuations" type="xsd:integer">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The maximum number of flow execution continuations (snapshots) allowed by this repository per conversation.
This attribute is only relevant when the repository type is 'continuation'.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="conversation-manager-ref" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The idref of the conversation manager this repository should use. Setting this attribute
allows full control over the conversation manager implementation and configuration.
When used, any value for the 'max-conversations' attribute is ignored.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="execution-listenersType">
<xsd:sequence>
<xsd:element name="listener" type="listenerType" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Deploys a single flow execution listener that will observe the execution lifecycle of one or more
flow definitions. The flow definitions this listener applies to may be restricted by specifying criteria.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="listenerType">
<xsd:attribute name="ref" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The idref to your flow execution listener.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="criteria" type="xsd:string" default="*">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The flow definitions your listener should apply to, delimited by commas or '*' for "all".
Example: 'flow1,flow2,flow3'.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="execution-attributesType">
<xsd:sequence>
<xsd:element name="alwaysRedirectOnPause" type="alwaysRedirectOnPauseType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Sets the 'alwaysRedirectOnPause' execution attribute value. 'alwaysRedirectOnPause' allows
control over whether each time a flow execution pauses a browser redirect is performed. If
not specified the default value is 'true' unless explicitly set otherwise.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="attribute" type="attributeType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
A single attribute describing an element. Attributes have string keys and object values.
An attribute's type may be specified using the 'type' attribute.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="alwaysRedirectOnPauseType">
<xsd:attribute name="value" type="xsd:boolean" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
true = always redirect on pause; false = do not, only redirect when explicitly instructed by the flow definition.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="attributeType">
<xsd:attribute name="name" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The name of the attribute.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="type" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The attribute's type, used to perform a from-string type conversion if specified.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="value" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The attribute value, subject to type conversion if the 'type' attribute is defined.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:schema>

View File

@@ -18,21 +18,9 @@ Provides an easy way to configure a flow executor and an XML flow definition reg
</xsd:documentation>
</xsd:annotation>
<xsd:import namespace="http://www.springframework.org/schema/beans"
schemaLocation="http://www.springframework.org/schema/beans/spring-beans-2.0.xsd" />
<xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-2.0.xsd" />
<xsd:element name="enable-scopes">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Allows access to the Spring Web Flow scopes (conversation, flow, flash) from a Spring ApplicationContext.
Only one of these elements is required per ApplicationContext.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="registry">
<xsd:element name="flow-registry">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
@@ -48,11 +36,11 @@ A flow registry is used by a flow executor at runtime to launch new executions o
]]>
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:sequence>
<xsd:element name="location" type="locationType" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:sequence>
<xsd:element name="flow-location" type="flowLocationType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
@@ -61,7 +49,7 @@ resource will be registered in this registry.
<br>
Individual paths such as:
<pre>
/WEB-INF/flows/orderitem-flow.xml
/WEB-INF/booking.xml
</pre>
... are supported as well as wildcard paths such as:
<pre>
@@ -70,55 +58,43 @@ Individual paths such as:
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="class" type="classType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Specifies a class containing a flow definition. The flow definition built from this
class will be registered in this registry.
<br>
Classes must be declared using a fully qualified path name such as
<pre>
com.interface21.SellItemFlow
</pre>
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="crud" type="crudType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Specifies a domain entity class to have crud flows built for. This will register flows for Creating, Reading, Updating,
and Deleting the domain entity in a namespace that is equal to the short name of the class.
<br>
Entities must be declared using a fully qualified path name such as
<pre>
com.interface21.Account
</pre>
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="namespace" type="namespaceType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Specifies a namespace to register nested flow definitions in. This namespace is a course-grained grouping for uniquely
identifying a flow. Since some flows define a namespace implicitly (CRUD flows for example), not all flow types are
allowed to be nested in the namespace tag.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:extension>
</xsd:element>
<xsd:element name="flow-builder" type="flowBuilderType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Specifies a flow builder implementation to register in the registry.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="flow-builder-services" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The idref of the flow builder services instance this flow definition registry should use.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="locationType">
<xsd:complexType name="flowLocationType">
<xsd:sequence>
<xsd:element name="flow-definition-attributes" type="flowDefinitionAttributesType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Attributes to assign to the flow definition that may influence flow execution.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
@@ -140,82 +116,42 @@ that matches multiple resources.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="classType">
<xsd:attribute name="name" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The name of the java based flow definition.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="crudType">
<xsd:attribute name="entity" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The name of a domain entity class to have crud flows created for.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="namespaceType">
<xsd:sequence>
<xsd:element name="location" type="locationType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Specifies a path to a externalized flow definition resource. The flow definition built from this
resource will be registered in this registry.
<br>
Individual paths such as:
<pre>
/WEB-INF/flows/orderitem-flow.xml
</pre>
... are supported as well as wildcard paths such as:
<pre>
/WEB-INF/flows/**/*-flow.xml
</pre>
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="class" type="classType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Specifies a class containing a flow definition. The flow definition built from this
class will be registered in this registry.
<br>
Classes must be declared using a fully qualified path name such as
<pre>
com.interface21.SellItemFlow
</pre>
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The name of the namespace to group all child flow definitions in.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:element name="executor">
<xsd:complexType name="flowBuilderType">
<xsd:sequence>
<xsd:element name="flow-definition-attributes" type="flowDefinitionAttributesType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Attributes to assign to the flow definition that may influence flow execution.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The id to the externalized flow definition built by this builder.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="class" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The fully qualified class name of the flow builder implementation.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:element name="flow-executor">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
@@ -230,7 +166,7 @@ an execution repository.
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:sequence>
<xsd:element name="repository" type="repositoryType" minOccurs="0">
<xsd:element name="flow-execution-repository" type="flowExecutionRepositoryType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
@@ -240,7 +176,7 @@ to be more fine grained than the repositoryType attribute on executor.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="execution-attributes" type="execution-attributesType" minOccurs="0">
<xsd:element name="flow-execution-attributes" type="flowExecutionAttributesType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
@@ -250,7 +186,7 @@ These attributes may influence execution behavior.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="execution-listeners" type="execution-listenersType" minOccurs="0">
<xsd:element name="flow-execution-listeners" type="flowExecutionListenersType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
@@ -260,17 +196,7 @@ The listeners eligible for observing the lifecycle of executions launched by thi
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="repository-type" type="repositoryTypeAttribute">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The type of execution repository to use. The repository is responsible for managing flow execution
persistence between requests.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="registry-ref" type="xsd:string" use="required">
<xsd:attribute name="flow-registry" type="xsd:string" default="flowRegistry">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
@@ -283,54 +209,9 @@ The idref to the registry this executor will use to locate flow definitions for
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:simpleType name="repositoryTypeAttribute">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="continuation">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The continuation repository type. Use this to snapshot flow execution state server-side to support use
of the browser back button.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="simple">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The simple repository type. Use of this strategy incurs minimal storage overhead but explicitly prevents
resubmits using the browser back button.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="client">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The client continuation repository type. Use this to snapshot flow execution state client-side to support use of the browser
back button in a stateless server environment.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="singlekey">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The "single key" repository type. Use of this strategy assigns a single key per flow execution that remains
constant throughout the life of the execution.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="repositoryType">
<xsd:attribute name="type" type="repositoryTypeAttribute" use="required">
<xsd:complexType name="flowExecutionRepositoryType">
<xsd:attribute name="type" type="flowExecutionRepositoryTypeAttribute" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
@@ -345,7 +226,7 @@ persistence between requests.
<xsd:documentation>
<![CDATA[
The maximum number of concurrent conversations allowed by this repository. It is illegal to set this
attribute if you are also setting the 'conversation-manager-ref' attribute.
attribute if you are also setting the 'conversation-manager' attribute.
]]>
</xsd:documentation>
</xsd:annotation>
@@ -360,7 +241,7 @@ This attribute is only relevant when the repository type is 'continuation'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="conversation-manager-ref" type="xsd:string">
<xsd:attribute name="conversation-manager" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
@@ -372,10 +253,55 @@ When used, any value for the 'max-conversations' attribute is ignored.
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="execution-listenersType">
<xsd:simpleType name="flowExecutionRepositoryTypeAttribute">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="continuation">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The continuation repository type. Use this to snapshot flow execution state server-side to support use
of the browser back button.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="simple">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The simple repository type. Use of this strategy incurs minimal storage overhead but explicitly prevents
resubmits using the browser back button.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="client">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The client continuation repository type. Use this to snapshot flow execution state client-side to support use of the browser
back button in a stateless server environment.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="singlekey">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The "single key" repository type. Use of this strategy assigns a single key per flow execution that remains
constant throughout the life of the execution.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="flowExecutionListenersType">
<xsd:sequence>
<xsd:element name="listener" type="listenerType" maxOccurs="unbounded">
<xsd:element name="listener" type="flowExecutionListenerType" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
@@ -388,7 +314,7 @@ flow definitions. The flow definitions this listener applies to may be restrict
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="listenerType">
<xsd:complexType name="flowExecutionListenerType">
<xsd:attribute name="ref" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
@@ -410,7 +336,7 @@ Example: 'flow1,flow2,flow3'.
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="execution-attributesType">
<xsd:complexType name="flowExecutionAttributesType">
<xsd:sequence>
<xsd:element name="alwaysRedirectOnPause" type="alwaysRedirectOnPauseType" minOccurs="0">
<xsd:annotation>
@@ -447,7 +373,22 @@ true = always redirect on pause; false = do not, only redirect when explicitly i
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="flowDefinitionAttributesType">
<xsd:sequence>
<xsd:element name="attribute" type="attributeType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
A single attribute describing an element. Attributes have string keys and object values.
An attribute's type may be specified using the 'type' attribute.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="attributeType">
<xsd:attribute name="name" type="xsd:string" use="required">
<xsd:annotation>

View File

@@ -0,0 +1,74 @@
package org.springframework.webflow.context;
import org.springframework.webflow.core.collection.ParameterMap;
/**
* An abstract representation request to be sent into the Spring Web Flow system at a later date. Used to request
* operations such as flow definition redirects, flow execution redirects, as well as to generate flow definition and
* flow execution URLs.
*
* @author Keith Donald
*/
public abstract class AbstractFlowRequestInfo {
/**
* The flow definition the request should be sent to.
*/
private String flowDefinitionId;
/**
* Hierarchical data to be sent along in the request.
*/
private RequestPath requestPath;
/**
* Query parameters to be sent along in the request.
*/
private ParameterMap requestParameters;
/**
* An anchor fragment to be sent in the request, for interpretation by the agent that initiates the request.
*/
private String fragment;
protected AbstractFlowRequestInfo(String flowDefinitionId, RequestPath requestPath, ParameterMap requestParameters,
String fragment) {
this.flowDefinitionId = flowDefinitionId;
this.requestPath = requestPath;
this.requestParameters = requestParameters;
this.fragment = fragment;
}
/**
* Returns the flow definition this request should be sent to.
* @return the flow definition identifier
*/
public String getFlowDefinitionId() {
return flowDefinitionId;
}
/**
* Returns hierarchical data to be sent along in the request.
* @return the request path
*/
public RequestPath getRequestPath() {
return requestPath;
}
/**
* Returns query parameters to send along in the request.
* @return the request parameters
*/
public ParameterMap getRequestParameters() {
return requestParameters;
}
/**
* Returns the fragment to be sent along in the request. A fragment is a free-form string value that may be
* interpreted by the client agent that initiates the request.
* @return the fragment
*/
public String getFragment() {
return fragment;
}
}

View File

@@ -15,12 +15,15 @@
*/
package org.springframework.webflow.context;
import java.io.PrintWriter;
import org.springframework.webflow.core.FlowException;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.core.collection.ParameterMap;
import org.springframework.webflow.core.collection.SharedAttributeMap;
/**
* A facade that provides normalized access to an external system that has interacted with Spring Web Flow.
* A facade that provides normalized access to an external system that has called into the Spring Web Flow system.
* <p>
* This context object provides a normalized interface for internal web flow artifacts to use to reason on and
* manipulate the state of an external actor calling into SWF to execute flows. It represents the context about a
@@ -36,22 +39,31 @@ import org.springframework.webflow.core.collection.SharedAttributeMap;
public interface ExternalContext {
/**
* Returns the path (or identifier) of the application that is executing.
* @return the application context path (e.g. "/myapp")
* Returns the unique id of the flow definition invoked by this caller. For a "launch" request, this identifier is
* used directly to create a new flow execution. For a "resume" request, this identifier provides additional flow
* execution context.
* @return the flow definition identifier, never null
* @see #getFlowExecutionKey()
*/
public String getContextPath();
public String getFlowId();
/**
* Returns the path (or identifier) of the dispatcher <i>within</i> the application that dispatched this request.
* @return the dispatcher path (e.g. "/dispatcher")
* Returns the unique key identifying the flow execution this client wishes to resume.
* @return the flow execution key, may be null if this is not a resume request
*/
public String getDispatcherPath();
public String getFlowExecutionKey();
/**
* Returns the path info of this external request. Could be null.
* @return the request path info (e.g. "/flows.htm")
* Returns the type of this client request. For example, this request may be a "GET" request or a "POST" request.
* @return the request method
*/
public String getRequestPathInfo();
public String getRequestMethod();
/**
* Returns the path of this request as a ordered list of fields.
* @return the elements of the request path
*/
public RequestPath getRequestPath();
/**
* Provides access to the parameters associated with the user request that led to SWF being called. This map is
@@ -92,4 +104,105 @@ public interface ExternalContext {
*/
public SharedAttributeMap getApplicationMap();
/**
* Provides access to the context object for the current environment.
* @return the environment specific context object
*/
public Object getContext();
/**
* Provides access to the request object for the current environment.
* @return the environment specific request object.
*/
public Object getRequest();
/**
* Provides access to the response object for the current environment.
* @return the environment specific response object.
*/
public Object getResponse();
/**
* Get a writer for writing out a response.
* @return the writer
*/
public PrintWriter getResponseWriter();
/**
* Builds a context-relative flow definition URL, suitable for rendering links that a launch new execution of a flow
* definition when accessed.
* @param requestInfo data needed to build the flow definition path
* @return the generated flow definition URL
*/
public String buildFlowDefinitionUrl(FlowDefinitionRequestInfo requestInfo);
/**
* Builds a flow execution URL, suitable for rendering links that resume a paused flow execution when accessed.
* @param requestInfo data needed to build the flow execution URL
* @param contextRelative whether the URL returned should be relative to this external context or absolute.
* @return the generated flow execution URL
*/
public String buildFlowExecutionUrl(FlowExecutionRequestInfo requestInfo, boolean contextRelative);
/**
* Encode the provided string using the encoding scheme of this external context.
* @param string the string
* @return the encoded string
*/
public String encode(String string);
/**
* Request that a flow execution redirect be sent as the response. A flow execution redirect tells the caller to
* resume a flow execution in a new request. Sets response committed to true.
* @param requestInfo data needed to issue the flow execution redirect
* @see #isResponseCommitted()
*/
public void sendFlowExecutionRedirect(FlowExecutionRequestInfo requestInfo);
/**
* Request that a flow definition redirect be sent as the response. A flow definition redirect tells the caller to
* start a new execution of the flow definition with the input provided.
* @param requestInfo data needed to issue the flow definition redirect
*/
public void sendFlowDefinitionRedirect(FlowDefinitionRequestInfo requestInfo);
/**
* Request that a external redirect be sent as the response. An external redirect tells the caller to access the
* resource at the given resource URL. Sets response committed to true. Note: no special encoding is performed on
* the string argument. Callers must perform their own encoding when necessary.
* @param resourceUrl the resource URL string
* @see #isResponseCommitted()
*/
public void sendExternalRedirect(String resourceUrl);
/**
* Report that flow execution request processing ended with a "paused" result, indicating the flow execution paused
* and will be waiting to resume on a subsequent request.
* @param flowExecutionKey the flow execution key
*/
public void setPausedResult(String flowExecutionKey);
/**
* Report that flow execution request processing ended with a "ended" result, indicating the flow execution
* terminated.
* @param flowExecutionKey the flow execution key, now invalid or null if never assigned
*/
public void setEndedResult(String flowExecutionKey);
/**
* Report that flow execution request processing ended with a flow exception.
* @param e the flow exception
*/
public void setExceptionResult(FlowException e);
/**
* Returns true if the current request has already provisioned the response that will be sent back to the calling
* system.
* @return true if the response has been committed, false otherwise
* @see #sendFlowExecutionRedirect(FlowExecutionRequestInfo)
* @see #sendFlowDefinitionRedirect(FlowDefinitionRequestInfo)
* @see #sendExternalRedirect(String)
*/
public boolean isResponseCommitted();
}

View File

@@ -0,0 +1,25 @@
package org.springframework.webflow.context;
import org.springframework.util.Assert;
import org.springframework.webflow.core.collection.ParameterMap;
/**
* A request to launch a new execution of a flow definition.
*
* @author Keith Donald
*/
public class FlowDefinitionRequestInfo extends AbstractFlowRequestInfo {
/**
* Creates a new flow definition request.
* @param flowDefinitionId the flow definition id (required)
* @param requestPath the request path (optional)
* @param requestParameters the request parameters (optional)
* @param fragment the fragment (optional)
*/
public FlowDefinitionRequestInfo(String flowDefinitionId, RequestPath requestPath, ParameterMap requestParameters,
String fragment) {
super(flowDefinitionId, requestPath, requestParameters, fragment);
Assert.hasText(flowDefinitionId, "The id of the flow definition to redirect to is required");
}
}

View File

@@ -0,0 +1,51 @@
package org.springframework.webflow.context;
import org.springframework.util.Assert;
import org.springframework.webflow.core.collection.ParameterMap;
/**
* A request to resume an existing flow execution that is currently paused.
*
* @author Keith Donald
*/
public class FlowExecutionRequestInfo extends AbstractFlowRequestInfo {
/**
* The key of the flow execution to resume.
*/
private String flowExecutionKey;
/**
* Creates a new request to resume a flow execution.
* @param flowDefinitionId the definition identifier of the execution (required)
* @param flowExecutionKey the key of the flow execution (required)
*/
public FlowExecutionRequestInfo(String flowDefinitionId, String flowExecutionKey) {
this(flowDefinitionId, flowExecutionKey, null, null, null);
}
/**
* Creates a new request to resume a flow execution.
* @param flowDefinitionId the definition identifier of the execution (required)
* @param flowExecutionKey the key of the flow execution (required)
* @param requestPath the request path (optional)
* @param requestParameters the request parameters (optional)
* @param fragment the fragment (optional)
*/
public FlowExecutionRequestInfo(String flowDefinitionId, String flowExecutionKey, RequestPath requestPath,
ParameterMap requestParameters, String fragment) {
super(flowDefinitionId, requestPath, requestParameters, fragment);
Assert.hasText(flowDefinitionId,
"The definition identifier of the flow execution to redirect to cannot be null");
Assert.hasText(flowExecutionKey, "The flow execution key to redirect to cannot be null");
this.flowExecutionKey = flowExecutionKey;
}
/**
* Returns the unique key of the flow execution to resume.
* @return the flow execution key
*/
public String getFlowExecutionKey() {
return flowExecutionKey;
}
}

View File

@@ -0,0 +1,94 @@
package org.springframework.webflow.context;
import java.util.Arrays;
import org.springframework.util.Assert;
/**
* A representation of a flow request path. In a flow definition request URI,the request path is the portion after the
* flow definition identifier. For example, in a URI of http://host/booking-flow/1 the request path is "/1".
*
* @author Keith Donald
*/
public class RequestPath {
private String[] elements;
public RequestPath(String path) {
Assert.notNull(path, "The path string cannot be null");
Assert.isTrue(path.startsWith("/"), "The path must start with a '/'");
this.elements = path.substring(1).split("/");
}
private RequestPath(String[] elements) {
this.elements = elements;
}
public RequestPath pop(int elementCount) {
if (elementCount < elements.length) {
String[] newElements = new String[elements.length - elementCount];
System.arraycopy(elements, elementCount, newElements, 0, newElements.length);
return new RequestPath(newElements);
} else {
return null;
}
}
public RequestPath pop() {
return pop(1);
}
public int getElementCount() {
return elements.length;
}
public String getFirstElement() {
return getElement(0);
}
/**
* Get the elements of the request path.
* @return parsed request path elements
*/
public String[] getElements() {
return elements;
}
/**
* Gets the path element at the specified index.
* @param index the element index
* @return the element
* @throws IndexOutOfBoundsException if the index is out of bounds
*/
public String getElement(int index) throws IndexOutOfBoundsException {
return elements[index];
}
public boolean equals(Object o) {
if (!(o instanceof RequestPath)) {
return false;
}
RequestPath other = (RequestPath) o;
return Arrays.equals(this.elements, other.elements);
}
public int hashCode() {
return Arrays.hashCode(elements);
}
public static RequestPath valueOf(String[] elements) {
return new RequestPath(elements);
}
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("/");
for (int i = 0; i < elements.length; i++) {
buffer.append(elements[i]);
if (i < (elements.length - 1)) {
buffer.append("/");
}
}
return buffer.toString();
}
}

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.webflow.context.portlet;
import java.io.PrintWriter;
import java.util.Map;
import javax.portlet.PortletContext;
@@ -24,6 +25,10 @@ import javax.portlet.PortletSession;
import org.springframework.core.style.ToStringCreator;
import org.springframework.webflow.context.ExternalContext;
import org.springframework.webflow.context.FlowDefinitionRequestInfo;
import org.springframework.webflow.context.FlowExecutionRequestInfo;
import org.springframework.webflow.context.RequestPath;
import org.springframework.webflow.core.FlowException;
import org.springframework.webflow.core.collection.LocalAttributeMap;
import org.springframework.webflow.core.collection.LocalParameterMap;
import org.springframework.webflow.core.collection.LocalSharedAttributeMap;
@@ -103,20 +108,6 @@ public class PortletExternalContext implements ExternalContext {
this.userInfoMap = userInfo != null ? new LocalAttributeMap(userInfo) : null;
}
public String getContextPath() {
return request.getContextPath();
}
public String getDispatcherPath() {
// returns null in a portlet environment
return null;
}
public String getRequestPathInfo() {
// returns null in a portlet environment
return null;
}
public ParameterMap getRequestParameterMap() {
return requestParameterMap;
}
@@ -148,25 +139,101 @@ public class PortletExternalContext implements ExternalContext {
/**
* Returns the wrapped Portlet context.
*/
public PortletContext getContext() {
public Object getContext() {
return context;
}
/**
* Returns the wrapped Portlet request.
*/
public PortletRequest getRequest() {
public Object getRequest() {
return request;
}
/**
* Returns the wrapped Portlet response.
*/
public PortletResponse getResponse() {
public Object getResponse() {
return response;
}
public String getFlowId() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Auto-generated method stub");
}
public String getFlowExecutionKey() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Auto-generated method stub");
}
public RequestPath getRequestPath() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Auto-generated method stub");
}
public String getRequestMethod() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Auto-generated method stub");
}
public PrintWriter getResponseWriter() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Auto-generated method stub");
}
public String buildFlowDefinitionUrl(FlowDefinitionRequestInfo urlInfo) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Auto-generated method stub");
}
public String buildFlowExecutionUrl(FlowExecutionRequestInfo urlInfo, boolean contextRelative) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Auto-generated method stub");
}
public String encode(String string) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Auto-generated method stub");
}
public void sendFlowDefinitionRedirect(FlowDefinitionRequestInfo urlInfo) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Auto-generated method stub");
}
public void sendFlowExecutionRedirect(FlowExecutionRequestInfo urlInfo) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Auto-generated method stub");
}
public void sendExternalRedirect(String resourceUrl) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Auto-generated method stub");
}
public void setPausedResult(String flowExecutionKey) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Auto-generated method stub");
}
public void setEndedResult(String flowExecutionKey) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Auto-generated method stub");
}
public void setExceptionResult(FlowException e) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Auto-generated method stub");
}
public boolean isResponseCommitted() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Auto-generated method stub");
}
public String toString() {
return new ToStringCreator(this).append("requestParameterMap", getRequestParameterMap()).toString();
}
}

View File

@@ -15,18 +15,34 @@
*/
package org.springframework.webflow.context.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.core.style.ToStringCreator;
import org.springframework.webflow.context.AbstractFlowRequestInfo;
import org.springframework.webflow.context.ExternalContext;
import org.springframework.webflow.context.ExternalContextHolder;
import org.springframework.webflow.context.FlowDefinitionRequestInfo;
import org.springframework.webflow.context.FlowExecutionRequestInfo;
import org.springframework.webflow.context.RequestPath;
import org.springframework.webflow.core.FlowException;
import org.springframework.webflow.core.collection.LocalAttributeMap;
import org.springframework.webflow.core.collection.LocalParameterMap;
import org.springframework.webflow.core.collection.LocalSharedAttributeMap;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.core.collection.ParameterMap;
import org.springframework.webflow.core.collection.SharedAttributeMap;
import org.springframework.webflow.executor.FlowExecutor;
/**
* Provides contextual information about an HTTP Servlet environment that has interacted with Spring Web Flow.
@@ -36,6 +52,9 @@ import org.springframework.webflow.core.collection.SharedAttributeMap;
*/
public class ServletExternalContext implements ExternalContext {
/** The default encoding scheme: UTF-8 */
private static final String DEFAULT_ENCODING_SCHEME = "UTF-8";
/**
* The context.
*/
@@ -71,32 +90,61 @@ public class ServletExternalContext implements ExternalContext {
*/
private SharedAttributeMap applicationMap;
private String flowId;
private String flowExecutionKey;
private RequestPath requestPath;
private String encodingScheme = DEFAULT_ENCODING_SCHEME;
private FlowExecutionRedirector flowExecutionRedirector;
private FlowDefinitionRedirector flowDefinitionRedirector;
private String resourceUri;
private short result;
private String processedFlowExecutionKey;
private FlowException exception;
/**
* Create a new external context wrapping given servlet HTTP request and response and given servlet context.
* @param context the servlet context
* @param request the HTTP request
* @param response the HTTP response
* @param request the servlet request
* @param response the servlet response
*/
public ServletExternalContext(ServletContext context, HttpServletRequest request, HttpServletResponse response) {
public ServletExternalContext(ServletContext context, ServletRequest request, ServletResponse response) {
this.context = context;
this.request = request;
this.response = response;
this.requestParameterMap = new LocalParameterMap(new HttpServletRequestParameterMap(request));
this.requestMap = new LocalAttributeMap(new HttpServletRequestMap(request));
this.sessionMap = new LocalSharedAttributeMap(new HttpSessionMap(request));
try {
this.request = (HttpServletRequest) request;
this.response = (HttpServletResponse) response;
} catch (ClassCastException e) {
throw new IllegalArgumentException("Request and response objects must be HTTP objects", e);
}
this.requestParameterMap = new LocalParameterMap(new HttpServletRequestParameterMap(this.request));
this.requestMap = new LocalAttributeMap(new HttpServletRequestMap(this.request));
this.sessionMap = new LocalSharedAttributeMap(new HttpSessionMap(this.request));
this.applicationMap = new LocalSharedAttributeMap(new HttpServletContextMap(context));
parseRequestPathInfo();
}
public String getContextPath() {
return request.getContextPath();
public String getFlowId() {
return flowId;
}
public String getDispatcherPath() {
return request.getServletPath();
public String getFlowExecutionKey() {
return flowExecutionKey;
}
public String getRequestPathInfo() {
return request.getPathInfo();
public String getRequestMethod() {
return request.getMethod();
}
public RequestPath getRequestPath() {
return requestPath;
}
public ParameterMap getRequestParameterMap() {
@@ -119,28 +167,268 @@ public class ServletExternalContext implements ExternalContext {
return applicationMap;
}
/**
* Return the wrapped HTTP servlet context.
*/
public ServletContext getContext() {
public Object getContext() {
return context;
}
/**
* Return the wrapped HTTP servlet request.
*/
public HttpServletRequest getRequest() {
public Object getRequest() {
return request;
}
/**
* Return the wrapped HTTP servlet response.
*/
public HttpServletResponse getResponse() {
public Object getResponse() {
return response;
}
public boolean isResponseCommitted() {
return flowExecutionRedirector != null || flowDefinitionRedirector != null || resourceUri != null;
}
// response requesters
public PrintWriter getResponseWriter() {
try {
return response.getWriter();
} catch (IOException e) {
// TODO - handle how?
throw new RuntimeException(e);
}
}
public void sendFlowExecutionRedirect(FlowExecutionRequestInfo request) {
flowExecutionRedirector = new FlowExecutionRedirector(request);
}
public void sendFlowDefinitionRedirect(FlowDefinitionRequestInfo request) {
flowDefinitionRedirector = new FlowDefinitionRedirector(request);
}
public void sendExternalRedirect(String resourceUri) {
this.resourceUri = resourceUri;
}
// helpers
public String encode(String string) {
try {
return URLEncoder.encode(string, encodingScheme);
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Unsupported encoding errors should never happen", e);
}
}
public String buildFlowDefinitionUrl(FlowDefinitionRequestInfo requestInfo) {
return request.getContextPath() + request.getServletPath() + "/" + requestInfo.getFlowDefinitionId()
+ requestPath(requestInfo) + requestParameters(requestInfo) + fragment(requestInfo);
}
public String buildFlowExecutionUrl(FlowExecutionRequestInfo requestInfo, boolean contextRelative) {
String contextRelativeUrl = request.getContextPath() + request.getServletPath() + "/executions/"
+ requestInfo.getFlowDefinitionId() + "/" + requestInfo.getFlowExecutionKey()
+ requestPath(requestInfo) + requestParameters(requestInfo) + fragment(requestInfo);
if (contextRelative) {
return contextRelativeUrl;
} else {
return request.getScheme() + request.getServerName() + contextRelativeUrl;
}
}
// execution processing result setters
public void setPausedResult(String flowExecutionKey) {
result = 0;
processedFlowExecutionKey = flowExecutionKey;
}
public void setEndedResult(String flowExecutionKey) {
result = 1;
processedFlowExecutionKey = flowExecutionKey;
}
public void setExceptionResult(FlowException e) {
result = 2;
exception = e;
processedFlowExecutionKey = flowExecutionKey;
}
/**
* Execute the flow request lifecycle, including provision of the final response.
* @param flowExecutor the flow executor for calling into the Spring Web Flow system
* @throws IOException an IOException occurred issuing the response
*/
public void executeFlowRequest(FlowExecutor flowExecutor) throws IOException {
ExternalContextHolder.setExternalContext(this);
try {
flowExecutor.execute(this);
if (isPausedResult()) {
if (flowExecutionRedirector != null) {
flowExecutionRedirector.issueRedirect();
} else if (flowDefinitionRedirector != null) {
flowDefinitionRedirector.issueRedirect();
} else if (resourceUri != null) {
sendRedirect(resourceUri);
} else {
// commit response?
}
} else if (isEndResult()) {
if (flowExecutionRedirector != null) {
if (flowExecutionRedirector.redirectsTo(processedFlowExecutionKey)) {
throw new IllegalStateException(
"You cannot send a flow execution redirect when this execution has ended - programmer error");
} else {
flowExecutionRedirector.issueRedirect();
}
} else if (flowDefinitionRedirector != null) {
flowDefinitionRedirector.issueRedirect();
} else if (resourceUri != null) {
sendRedirect(resourceUri);
} else {
// commit response?
}
} else if (isExceptionResult()) {
// response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, exception.getMessage());
throw exception;
}
} finally {
ExternalContextHolder.setExternalContext(null);
}
}
private void parseRequestPathInfo() {
String pathInfo = request.getPathInfo();
if (pathInfo == null) {
throw new IllegalArgumentException(
"The requestPathInfo is null: unable to extract flow definition id or flow execution key");
}
RequestPath path = new RequestPath(pathInfo);
if (path.getElement(0).equals("executions")) {
flowId = path.getElement(1);
flowExecutionKey = path.getElement(2);
if (path.getElementCount() > 3) {
requestPath = path.pop(3);
} else {
requestPath = null;
}
} else {
flowId = path.getElement(0);
if (path.getElementCount() > 1) {
requestPath = path.pop(1);
} else {
requestPath = null;
}
}
}
private boolean isPausedResult() {
return result == 0;
}
private boolean isEndResult() {
return result == 1;
}
private boolean isExceptionResult() {
return result == 2;
}
private String requestPath(AbstractFlowRequestInfo requestInfo) {
if (requestInfo.getRequestPath() == null) {
return "";
}
StringBuffer buffer = new StringBuffer();
String[] requestElements = requestInfo.getRequestPath().getElements();
for (int i = 0; i < requestElements.length; i++) {
buffer.append('/').append(encode(requestElements[i], encodingScheme));
}
return buffer.toString();
}
private String requestParameters(AbstractFlowRequestInfo requestInfo) {
if (requestInfo.getRequestParameters() == null) {
return "";
}
StringBuffer queryString = new StringBuffer();
queryString.append('?');
ParameterMap requestParameters = requestInfo.getRequestParameters();
Iterator it = requestParameters.asMap().entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String parameterName = encode((String) entry.getKey(), encodingScheme);
String parameterValue = encode((String) entry.getValue(), encodingScheme);
queryString.append(parameterName).append('=').append(parameterValue);
if (it.hasNext()) {
queryString.append('&');
}
}
return queryString.toString();
}
private String fragment(AbstractFlowRequestInfo requestInfo) {
if (requestInfo.getFragment() == null || requestInfo.getFragment().length() == 0) {
return "";
}
return "#" + requestInfo.getFragment();
}
private String encode(String value, String encodingScheme) {
try {
return URLEncoder.encode(value, encodingScheme);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
private void sendRedirect(String targetUrl) throws IOException {
response.sendRedirect(response.encodeRedirectURL(targetUrl));
}
public String toString() {
return new ToStringCreator(this).append("requestParameterMap", getRequestParameterMap()).toString();
}
private abstract class FlowRedirector {
private AbstractFlowRequestInfo requestInfo;
public FlowRedirector(AbstractFlowRequestInfo requestInfo) {
this.requestInfo = requestInfo;
}
protected AbstractFlowRequestInfo getRequestInfo() {
return requestInfo;
}
public abstract void issueRedirect() throws IOException;
}
private class FlowExecutionRedirector extends FlowRedirector {
public FlowExecutionRedirector(FlowExecutionRequestInfo requestInfo) {
super(requestInfo);
}
public boolean redirectsTo(String flowExecutionKey) {
FlowExecutionRequestInfo requestInfo = (FlowExecutionRequestInfo) getRequestInfo();
return requestInfo.getFlowExecutionKey().equals(flowExecutionKey);
}
public void issueRedirect() throws IOException {
FlowExecutionRequestInfo requestInfo = (FlowExecutionRequestInfo) getRequestInfo();
String targetUrl = request.getContextPath() + request.getServletPath() + "/executions/"
+ requestInfo.getFlowDefinitionId() + "/" + requestInfo.getFlowExecutionKey()
+ requestPath(getRequestInfo()) + requestParameters(getRequestInfo()) + fragment(getRequestInfo());
response.sendRedirect(response.encodeRedirectURL(targetUrl));
}
}
private class FlowDefinitionRedirector extends FlowRedirector {
public FlowDefinitionRedirector(FlowDefinitionRequestInfo redirect) {
super(redirect);
}
public void issueRedirect() throws IOException {
FlowDefinitionRequestInfo redirect = (FlowDefinitionRequestInfo) getRequestInfo();
String targetUrl = request.getContextPath() + request.getServletPath() + "/"
+ redirect.getFlowDefinitionId() + requestPath(getRequestInfo())
+ requestParameters(getRequestInfo()) + fragment(getRequestInfo());
response.sendRedirect(response.encodeRedirectURL(targetUrl));
}
}
}

View File

@@ -13,12 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.webflow.core;
package org.springframework.webflow.core.expression;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.binding.expression.ExpressionVariable;
import org.springframework.binding.expression.ParserException;
import org.springframework.binding.expression.SettableExpression;
/**
* Static helper factory that creates instances of the default expression parser used by Spring Web Flow when requested.
@@ -47,20 +47,22 @@ public final class DefaultExpressionParserFactory {
*/
public static synchronized ExpressionParser getExpressionParser() {
// return a wrapper that will lazily load the default expression parser
// this prevents the default OGNL-based parser from being intialized until it is actually used
// this prevents the default OGNL-based parser from being initialized until it is actually used
// which allows OGNL to be an optional dependency if the expression parser wrapper is replaced and never used
return new ExpressionParser() {
public boolean isDelimitedExpression(String expressionString) {
return getDefaultExpressionParser().isDelimitedExpression(expressionString);
public boolean isEvalExpressionString(String string) {
return getDefaultExpressionParser().isEvalExpressionString(string);
}
public Expression parseExpression(String expressionString) throws ParserException {
return getDefaultExpressionParser().parseExpression(expressionString);
public String parseEvalExpressionString(String string) throws ParserException {
return getDefaultExpressionParser().parseEvalExpressionString(string);
}
public SettableExpression parseSettableExpression(String expressionString) throws ParserException,
UnsupportedOperationException {
return getDefaultExpressionParser().parseSettableExpression(expressionString);
public Expression parseExpression(String expressionString, Class expressionTargetType,
Class expectedEvaluationResultType, ExpressionVariable[] expressionVariables)
throws ParserException {
return getDefaultExpressionParser().parseExpression(expressionString, expressionTargetType,
expectedEvaluationResultType, expressionVariables);
}
};
}
@@ -80,7 +82,7 @@ public final class DefaultExpressionParserFactory {
* Create the default expression parser.
* @return the default expression parser
*/
private static ExpressionParser createDefaultExpressionParser() {
private static ExpressionParser createDefaultExpressionParser() throws IllegalStateException {
try {
Class.forName("ognl.Ognl");
return new WebFlowOgnlExpressionParser();

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.webflow.core;
package org.springframework.webflow.core.expression;
import java.util.Map;

View File

@@ -0,0 +1,65 @@
package org.springframework.webflow.core.expression.el;
import java.util.Iterator;
import javax.el.ELContext;
import javax.el.ELResolver;
import javax.el.PropertyNotWritableException;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.RequestContextHolder;
/**
* Custom EL resolver that resolves to a thread-bound RequestContext object for binding expressions prefixed with a
* {@link #REQUEST_CONTEXT_VARIABLE_NAME}. For instance "#{requestContext.conversationScope.myProperty}".
* @author Jeremy Grelle
*/
public class RequestContextELResolver extends ELResolver {
/**
* Name of the request context variable.
*/
public static final String REQUEST_CONTEXT_VARIABLE_NAME = "requestContext";
public Class getCommonPropertyType(ELContext elContext, Object base) {
return Object.class;
}
public Iterator getFeatureDescriptors(ELContext elContext, Object base) {
return null;
}
public Class getType(ELContext elContext, Object base, Object property) {
if (base == null && REQUEST_CONTEXT_VARIABLE_NAME.equals(property)) {
elContext.setPropertyResolved(true);
return RequestContext.class;
} else {
return null;
}
}
public Object getValue(ELContext elContext, Object base, Object property) {
if (base == null && REQUEST_CONTEXT_VARIABLE_NAME.equals(property)) {
elContext.setPropertyResolved(true);
return RequestContextHolder.getRequestContext();
} else {
return null;
}
}
public boolean isReadOnly(ELContext elContext, Object base, Object property) {
if (base == null && REQUEST_CONTEXT_VARIABLE_NAME.equals(property)) {
elContext.setPropertyResolved(true);
return true;
} else {
return false;
}
}
public void setValue(ELContext elContext, Object base, Object property, Object value) {
if (base == null && REQUEST_CONTEXT_VARIABLE_NAME.equals(property)) {
elContext.setPropertyResolved(true);
throw new PropertyNotWritableException("The RequestContext cannot be set with an expression.");
}
}
}

View File

@@ -0,0 +1,150 @@
package org.springframework.webflow.core.expression.el;
import java.util.Iterator;
import javax.el.ELContext;
import javax.el.ELResolver;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.RequestContextHolder;
/**
* Custom EL resolver that searches the current request context for variables to resolve. The search algorithm looks in
* request scope first, then flash scope, then flow scope, then conversation scope.
*
* Suitable for use along side other variable resolvers to support EL binding expressions like "#{bean.property}" where
* "bean" could be a property in any supported scope.
*
* @author Jeremy Grelle
*/
public class ScopeSearchingELResolver extends ELResolver {
public Class getCommonPropertyType(ELContext elContext, Object base) {
return Object.class;
}
public Iterator getFeatureDescriptors(ELContext elContext, Object base) {
return null;
}
public Class getType(ELContext elContext, Object base, Object property) {
RequestContext requestContext;
if (base != null && base instanceof RequestContext) {
requestContext = (RequestContext) base;
} else if (base == null) {
requestContext = RequestContextHolder.getRequestContext();
} else {
return null;
}
if (requestContext == null) {
return null;
}
// flow execution is active: try request/flash/flow/conversation scope
String name = property.toString();
if (requestContext.getRequestScope().contains(name)) {
elContext.setPropertyResolved(true);
return requestContext.getRequestScope().get(name).getClass();
} else if (requestContext.getFlashScope().contains(name)) {
elContext.setPropertyResolved(true);
return requestContext.getFlashScope().get(name).getClass();
} else if (requestContext.getFlowScope().contains(name)) {
elContext.setPropertyResolved(true);
return requestContext.getFlowScope().get(name).getClass();
} else if (requestContext.getConversationScope().contains(name)) {
elContext.setPropertyResolved(true);
return requestContext.getConversationScope().get(name).getClass();
} else {
return null;
}
}
public Object getValue(ELContext elContext, Object base, Object property) {
RequestContext requestContext;
if (base != null && base instanceof RequestContext) {
requestContext = (RequestContext) base;
} else if (base == null) {
requestContext = RequestContextHolder.getRequestContext();
} else {
return null;
}
if (requestContext == null) {
return null;
}
String name = property.toString();
if (requestContext.getRequestScope().contains(name)) {
elContext.setPropertyResolved(true);
return requestContext.getRequestScope().get(name);
} else if (requestContext.getFlashScope().contains(name)) {
elContext.setPropertyResolved(true);
return requestContext.getFlashScope().get(name);
} else if (requestContext.getFlowScope().contains(name)) {
elContext.setPropertyResolved(true);
return requestContext.getFlowScope().get(name);
} else if (requestContext.getConversationScope().contains(name)) {
elContext.setPropertyResolved(true);
return requestContext.getConversationScope().get(name);
} else {
return null;
}
}
public boolean isReadOnly(ELContext elContext, Object base, Object property) {
RequestContext requestContext;
if (base != null && base instanceof RequestContext) {
requestContext = (RequestContext) base;
} else if (base == null) {
requestContext = RequestContextHolder.getRequestContext();
} else {
return false;
}
if (requestContext == null) {
return false;
}
// flow execution is active: try request/flash/flow/conversation scope
String name = property.toString();
if (requestContext.getRequestScope().contains(name)) {
elContext.setPropertyResolved(true);
return false;
} else if (requestContext.getFlashScope().contains(name)) {
elContext.setPropertyResolved(true);
return false;
} else if (requestContext.getFlowScope().contains(name)) {
elContext.setPropertyResolved(true);
return false;
} else if (requestContext.getConversationScope().contains(name)) {
elContext.setPropertyResolved(true);
return false;
} else {
return false;
}
}
public void setValue(ELContext elContext, Object base, Object property, Object value) {
RequestContext requestContext;
if (base != null && base instanceof RequestContext) {
requestContext = (RequestContext) base;
} else if (base == null) {
requestContext = RequestContextHolder.getRequestContext();
} else {
return;
}
if (requestContext == null) {
return;
}
// flow execution is active: try request/flash/flow/conversation scope
String name = property.toString();
if (requestContext.getRequestScope().contains(name)) {
elContext.setPropertyResolved(true);
requestContext.getRequestScope().put(name, value);
} else if (requestContext.getFlashScope().contains(name)) {
elContext.setPropertyResolved(true);
requestContext.getFlashScope().put(name, value);
} else if (requestContext.getFlowScope().contains(name)) {
elContext.setPropertyResolved(true);
requestContext.getFlowScope().put(name, value);
} else if (requestContext.getConversationScope().contains(name)) {
elContext.setPropertyResolved(true);
requestContext.getConversationScope().put(name, value);
}
}
}

View File

@@ -0,0 +1,73 @@
package org.springframework.webflow.core.expression.el;
import java.util.ArrayList;
import java.util.List;
import javax.el.ELContext;
import javax.el.ELResolver;
import javax.el.ExpressionFactory;
import javax.el.FunctionMapper;
import javax.el.VariableMapper;
import org.springframework.binding.expression.el.DefaultELResolver;
import org.springframework.binding.expression.el.ELContextFactory;
import org.springframework.binding.expression.el.ELExpressionParser;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.execution.RequestContext;
/**
* An ExpressionParser that allows Spring and Web Flow managed beans to be referenced in expressions in the
* FlowDefinition.
*
* @author Jeremy Grelle
*/
public class WebFlowELExpressionParser extends ELExpressionParser {
public WebFlowELExpressionParser(ExpressionFactory expressionFactory) {
super(expressionFactory);
putContextFactory(RequestContext.class, new RequestContextELContextFactory());
putContextFactory(MutableAttributeMap.class, new AttributeMapELContextFactory());
}
private static class RequestContextELContextFactory implements ELContextFactory {
public ELContext getELContext(Object target, VariableMapper variableMapper) {
List customResolvers = new ArrayList();
customResolvers.add(new RequestContextELResolver());
customResolvers.add(new ScopeSearchingELResolver());
ELResolver resolver = new DefaultELResolver(target, customResolvers);
return new WebFlowELContext(resolver, variableMapper);
}
}
private static class AttributeMapELContextFactory implements ELContextFactory {
public ELContext getELContext(Object target, VariableMapper variableMapper) {
ELResolver resolver = new DefaultELResolver(target, null);
return new WebFlowELContext(resolver, variableMapper);
}
}
private static class WebFlowELContext extends ELContext {
VariableMapper variableMapper;
ELResolver resolver;
public WebFlowELContext(ELResolver resolver, VariableMapper variableMapper) {
this.resolver = resolver;
this.variableMapper = variableMapper;
}
public ELResolver getELResolver() {
return resolver;
}
public FunctionMapper getFunctionMapper() {
return null;
}
public VariableMapper getVariableMapper() {
return variableMapper;
}
}
}

View File

@@ -1,180 +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.definition.registry;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.springframework.core.io.Resource;
import org.springframework.core.style.ToStringCreator;
import org.springframework.webflow.engine.builder.FlowServiceLocator;
/**
* A flow definition registrar that populates a flow definition registry from flow definitions defined within
* externalized resources. Encapsulates registration behavior common to all externalized registrars and is not tied to a
* specific flow definition format (e.g. xml).
* <p>
* Concrete subclasses are expected to derive from this class to provide knowledge about a particular kind of definition
* format by implementing the abstract template methods in this class.
*
* @see org.springframework.webflow.definition.registry.FlowDefinitionResource
* @see org.springframework.webflow.definition.registry.FlowDefinitionRegistry
*
* @author Keith Donald
* @author Ben Hale
*/
public abstract class ExternalizedFlowDefinitionRegistrar implements FlowDefinitionRegistrar {
/**
* The locator of services needed by flow definitions.
*/
private FlowServiceLocator flowServiceLocator;
/**
* A set of mappings between a namespace and a set of externalized flow definitions. A map of Strings to Sets
* containing {@link FlowDefinitionResource}s
*/
private Map namespaceFlowMappings;
/**
* The default namespace for flows registered without an explicit namespace.
*/
private String defaultNamespace = "";
/**
* Creates a new registrar with an empty initial set of namespace to flow mappings.
*/
public ExternalizedFlowDefinitionRegistrar() {
this(new HashMap());
}
/**
* Creates a new registrar with an initial set of namespace to flow mappings.
* @param namespaceFlowMappings the initial set of namespace to flow mappings
*/
public ExternalizedFlowDefinitionRegistrar(Map namespaceFlowMappings) {
this.namespaceFlowMappings = namespaceFlowMappings;
}
/**
* Sets the default namespace to register flows in. If not set the default namespace is "" (an empty string).
* @param defaultNamespace the default namespace
*/
public void setDefaultNamespace(String defaultNamespace) {
this.defaultNamespace = defaultNamespace;
}
public void setFlowServiceLocator(FlowServiceLocator flowServiceLocator) {
this.flowServiceLocator = flowServiceLocator;
}
/**
* Returns the flow service locator for use by subclasses.
*/
protected FlowServiceLocator getFlowServiceLocator() {
return flowServiceLocator;
}
/**
* Adds an externalized XML flow definition to be registered in the default namespace.
* @param location the resource to register
* @see #addLocation(Resource, String)
* @see #setDefaultNamespace(String)
*/
public boolean addLocation(Resource location) {
return addLocation(location, defaultNamespace);
}
/**
* Adds an externalized XML flow definition to be registered. The resource will be assigned a registry identifier
* equal to the filename of the resource, minus the filename extension. For example, an XML-based flow definition
* defined in the file <code>flow1.xml</code> will be identified as <code>flow1</code> in the registry created
* by this factory bean.
* @param location the resource to register
* @param namespace the namespace to register the flow definition in
*/
public boolean addLocation(Resource location, String namespace) {
return getFlows(namespace).add(new FlowDefinitionResource(location));
}
/**
* Adds an externalized XML flow definition resource to be registered in the default namespace.
* @param resource the flow definition resource to be registered
* @see #addResource(FlowDefinitionResource, String)
* @see #setDefaultNamespace(String)
*/
public boolean addResource(FlowDefinitionResource resource) {
return addResource(resource, defaultNamespace);
}
/**
* Adds an externalized XML flow definition resource to be registered.
* @param resource the flow definition resource to be registered
* @param namespace the namespace to register the flow definition in
*/
public boolean addResource(FlowDefinitionResource resource, String namespace) {
return getFlows(namespace).add(resource);
}
public void registerFlowDefinitions(FlowDefinitionRegistry registry) {
for (Iterator mappings = namespaceFlowMappings.entrySet().iterator(); mappings.hasNext();) {
Map.Entry mapping = (Map.Entry) mappings.next();
String namespace = (String) mapping.getKey();
for (Iterator resources = ((Set) mapping.getValue()).iterator(); resources.hasNext();) {
FlowDefinitionResource resource = (FlowDefinitionResource) resources.next();
register(resource, namespace, registry);
}
}
}
/**
* Registers a flow definition resource in a given namespace.
* @param resource the resource to register
* @param namespace the namespace to register in
* @param registry the registry
*/
private void register(FlowDefinitionResource resource, String namespace, FlowDefinitionRegistry registry) {
registry.registerFlowDefinition(createFlowDefinitionHolder(resource), namespace);
}
/**
* Returns the set of flows to be registered in a namespace.
* @param namespace The namespace for the collection to be returned
*/
private Set getFlows(String namespace) {
if (!namespaceFlowMappings.containsKey(namespace)) {
namespaceFlowMappings.put(namespace, new HashSet());
}
return (Set) namespaceFlowMappings.get(namespace);
}
// sub-classing hooks
/**
* Template factory method subclasses must override to return the holder for the flow definition to be registered
* loaded from the specified resource.
* @param resource the externalized resource
* @return the flow definition holder
*/
protected abstract FlowDefinitionHolder createFlowDefinitionHolder(FlowDefinitionResource resource);
public String toString() {
return new ToStringCreator(this).append("namespaceFlowMappings", namespaceFlowMappings).toString();
}
}

View File

@@ -23,28 +23,28 @@ import org.springframework.webflow.core.FlowException;
* @author Keith Donald
* @author Erwin Vervaet
*/
public abstract class FlowDefinitionConstructionException extends FlowException {
public class FlowDefinitionConstructionException extends FlowException {
/**
* The id of the flow that could not be constructed.
*/
private String flowId;
private String flowDefinitionId;
/**
* Creates an exception indicating a flow definition could not be constructed.
* @param flowId the flow id
* @param cause underlying cause of the exception
* @param flowDefinitionId the flow definition identifier
* @param cause the underlying cause of the exception
*/
public FlowDefinitionConstructionException(String flowId, Throwable cause) {
super("An exception occured constructing the flow with id '" + flowId + "'", cause);
this.flowId = flowId;
public FlowDefinitionConstructionException(String flowDefinitionId, Throwable cause) {
super("An exception occurred constructing the flow '" + flowDefinitionId + "'", cause);
this.flowDefinitionId = flowDefinitionId;
}
/**
* Returns the id of the flow definition that could not be constructed.
* @return the flow id
*/
public String getFlowId() {
return flowId;
public String getFlowDefinitionId() {
return flowDefinitionId;
}
}

View File

@@ -18,10 +18,8 @@ package org.springframework.webflow.definition.registry;
import org.springframework.webflow.definition.FlowDefinition;
/**
* A runtime service locator interface for retrieving flow definitions by <code>id</code>.
* <p>
* Flow locators are needed by flow executors at runtime to retrieve fully-configured flow definitions to support
* launching new flow executions.
* A runtime service locator interface for retrieving flow definitions by <code>id</code>. Flow locators are needed
* by flow executors at runtime to retrieve fully-configured flow definitions to support launching new flow executions.
*
* @author Keith Donald
* @author Erwin Vervaet
@@ -29,12 +27,12 @@ import org.springframework.webflow.definition.FlowDefinition;
public interface FlowDefinitionLocator {
/**
* Lookup the flow definition with the specified <code>path</code>.
* @param flowPath the flow definition path
* Lookup the flow definition with the specified id.
* @param id the flow definition identifier
* @return the flow definition
* @throws NoSuchFlowDefinitionException when the flow definition with the specified id does not exist
* @throws FlowDefinitionConstructionException if there is a problem constructing the identified flow definition
*/
public FlowDefinition getFlowDefinition(String flowPath) throws NoSuchFlowDefinitionException,
public FlowDefinition getFlowDefinition(String id) throws NoSuchFlowDefinitionException,
FlowDefinitionConstructionException;
}

View File

@@ -1,47 +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.definition.registry;
/**
* A strategy to use to populate a flow definition registry with one or more flow definitions.
* <p>
* Flow definition registrars encapsulate the knowledge about the source of a set of flow definition resources and the
* behavior necessary to add those resources to a flow definition registry.
* <p>
* The typical usage pattern is as follows:
* <ol>
* <li>Create a new (initially empty) flow definition registry.
* <li>Use any number of flow definition registrars to populate the registry by calling
* {@link #registerFlowDefinitions(FlowDefinitionRegistry)}.
* </ol>
* <p>
* This design where various registrars populate a generic registry was inspired by Spring's GenericApplicationContext,
* which can use any number of BeanDefinitionReaders to drive context population.
*
* @see FlowDefinitionRegistry
*
* @author Keith Donald
*/
public interface FlowDefinitionRegistrar {
/**
* Register flow definition resources managed by this registrar in the registry provided.
* @param registry the registry to register flow definitions in
*/
public void registerFlowDefinitions(FlowDefinitionRegistry registry);
}

View File

@@ -16,17 +16,15 @@
package org.springframework.webflow.definition.registry;
/**
* A container of flow definitions. Extends the {@link FlowDefinitionRegistryMBean} management interface exposing
* registry monitoring and management operations. Also extends {@link FlowDefinitionLocator} for accessing registered
* Flow definitions for execution at runtime.
* A container of flow definitions. Extends {@link FlowDefinitionLocator} for accessing registered Flow definitions for
* execution at runtime.
* <p>
* Flow definition registries can be configured with a "parent" registry to provide a hook into a larger flow definition
* registry hierarchy.
*
* @author Keith Donald
* @author Ben Hale
*/
public interface FlowDefinitionRegistry extends FlowDefinitionLocator, FlowDefinitionRegistryMBean {
public interface FlowDefinitionRegistry extends FlowDefinitionLocator {
/**
* Sets this registry's parent registry. When asked by a client to locate a flow definition this registry will query
@@ -39,17 +37,8 @@ public interface FlowDefinitionRegistry extends FlowDefinitionLocator, FlowDefin
* Register a flow definition in this registry. Registers a "holder", not the Flow definition itself. This allows
* the actual Flow definition to be loaded lazily only when needed, and also rebuilt at runtime when its underlying
* resource changes without re-deploy.
* @param flowHolder a holder holding the flow definition to register
* @param definitionHolder a holder holding the flow definition to register
*/
public void registerFlowDefinition(FlowDefinitionHolder flowHolder);
/**
* Register a flow definition in this registry under a specific namespace. Registers a "holder", not the Flow
* definition itself. This allows the actual Flow definition to be loaded lazily only when needed, and also rebuilt
* at runtime when its underlying resource changes without re-deploy.
* @param flowHolder a holder holding the flow definition to register
* @param namespace the namespace to register the flow definition in
*/
public void registerFlowDefinition(FlowDefinitionHolder flowHolder, String namespace);
public void registerFlowDefinition(FlowDefinitionHolder definitionHolder);
}

View File

@@ -15,9 +15,6 @@
*/
package org.springframework.webflow.definition.registry;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
@@ -29,13 +26,8 @@ import org.springframework.webflow.definition.FlowDefinition;
/**
* A generic registry implementation for housing one or more flow definitions.
* <p>
* This registry may be refreshed at runtime to "hot reload" refreshable flow definitions. Note that the refresh will
* only reload already registered flow definitions but will not detect any new flow definitions or remove flow
* definitions that no longer exist.
*
* @author Keith Donald
* @author Ben Hale
*/
public class FlowDefinitionRegistryImpl implements FlowDefinitionRegistry {
@@ -55,83 +47,22 @@ public class FlowDefinitionRegistryImpl implements FlowDefinitionRegistry {
flowDefinitions = new TreeMap();
}
// implementing FlowDefinitionRegistryMBean
public String[] getFlowDefinitionPaths() {
List flowPaths = new LinkedList();
for (Iterator namespaces = flowDefinitions.entrySet().iterator(); namespaces.hasNext();) {
Map.Entry namespaceEntry = (Map.Entry) namespaces.next();
String namespaceName = (String) namespaceEntry.getKey();
Map namespace = (Map) namespaceEntry.getValue();
for (Iterator ids = namespace.keySet().iterator(); ids.hasNext();) {
flowPaths.add(FlowPathUtils.buildFlowPath(namespaceName, (String) ids.next()));
}
}
return (String[]) flowPaths.toArray(new String[flowPaths.size()]);
}
public int getFlowDefinitionCount() {
int count = 0;
for (Iterator namespaces = flowDefinitions.values().iterator(); namespaces.hasNext();) {
Map namespace = (Map) namespaces.next();
count += namespace.size();
}
return count;
}
public boolean containsFlowDefinition(String flowPath) {
Assert.hasText(flowPath, "The flow path is required");
Map namespace = getNamespace(FlowPathUtils.extractFlowNamespace(flowPath));
return namespace.containsKey(FlowPathUtils.extractFlowId(flowPath));
}
public void refresh() throws FlowDefinitionConstructionException {
if (logger.isDebugEnabled()) {
logger.debug("Refreshing flow definition registry '" + this + "'");
}
for (Iterator namespaces = flowDefinitions.entrySet().iterator(); namespaces.hasNext();) {
Map.Entry namespaceEntry = (Map.Entry) namespaces.next();
String namespaceName = (String) namespaceEntry.getKey();
Map namespace = (Map) namespaceEntry.getValue();
for (Iterator ids = namespace.keySet().iterator(); ids.hasNext();) {
refresh(FlowPathUtils.buildFlowPath(namespaceName, (String) ids.next()));
}
}
}
public void refresh(String flowPath) throws NoSuchFlowDefinitionException, FlowDefinitionConstructionException {
if (logger.isDebugEnabled()) {
logger.debug("Refreshing flow with path '" + flowPath + "'");
}
ClassLoader loader = Thread.currentThread().getContextClassLoader();
try {
// workaround for JMX
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
FlowDefinitionHolder holder = getFlowDefinitionHolder(flowPath);
holder.refresh();
if (!holder.getFlowDefinitionId().equals(FlowPathUtils.extractFlowId(flowPath))) {
reindex(holder, FlowPathUtils.extractFlowNamespace(flowPath), flowPath);
}
} finally {
Thread.currentThread().setContextClassLoader(loader);
}
}
// implementing FlowDefinitionLocator
public FlowDefinition getFlowDefinition(String path) throws NoSuchFlowDefinitionException,
public FlowDefinition getFlowDefinition(String id) throws NoSuchFlowDefinitionException,
FlowDefinitionConstructionException {
Assert.hasText(path,
"Unable to load a flow definition: no flow path was provided. Please provide a valid flow path.");
if (logger.isDebugEnabled()) {
logger.debug("Getting flow definition with path '" + path + "'");
}
try {
return getFlowDefinitionHolder(path).getFlowDefinition();
if (id == null) {
throw new IllegalArgumentException("The id of the flow to lookup is required");
}
if (logger.isDebugEnabled()) {
logger.debug("Getting flow definition with id '" + id + "'");
}
return getFlowDefinitionHolder(id).getFlowDefinition();
} catch (NoSuchFlowDefinitionException e) {
if (parent != null) {
// try parent
return parent.getFlowDefinition(path);
return parent.getFlowDefinition(id);
}
throw e;
}
@@ -140,84 +71,32 @@ public class FlowDefinitionRegistryImpl implements FlowDefinitionRegistry {
// implementing FlowDefinitionRegistry
public void setParent(FlowDefinitionRegistry parent) {
if (logger.isDebugEnabled()) {
logger.debug("Setting parent flow definition registry to '" + parent + "'");
}
this.parent = parent;
}
public void registerFlowDefinition(FlowDefinitionHolder flowHolder) {
registerFlowDefinition(flowHolder, "");
public void registerFlowDefinition(FlowDefinitionHolder definitionHolder) {
Assert.notNull(definitionHolder, "The holder of the flow definition to register is required");
if (logger.isDebugEnabled()) {
logger.debug("Registering flow definition " + definitionHolder);
}
flowDefinitions.put(definitionHolder.getFlowDefinitionId(), definitionHolder);
}
public void registerFlowDefinition(FlowDefinitionHolder flowHolder, String namespace) {
Assert.notNull(flowHolder, "The flow definition holder to register is required");
Assert.notNull(namespace, "The flow namespace is required");
if (logger.isDebugEnabled()) {
logger.debug("Registering flow definition with id '" + flowHolder.getFlowDefinitionId()
+ "' in namespace '" + namespace + "'");
}
index(flowHolder, namespace);
}
/**
* Remove identified flow definition from this registry. If the given id is not known in this registry, nothing will
* happen.
* @param flowPath the flow definition path
*/
public void removeFlowDefinition(String flowPath) {
Assert.hasText(flowPath, "The flow path is required");
if (logger.isDebugEnabled()) {
logger.debug("Removing flow definition with path '" + flowPath + "'");
}
Map namespace = getNamespace(FlowPathUtils.extractFlowNamespace(flowPath));
namespace.remove(FlowPathUtils.extractFlowId(flowPath));
public void registerFlowDefinition(FlowDefinition definition) {
registerFlowDefinition(new StaticFlowDefinitionHolder(definition));
}
// internal helpers
/**
* Re-index given flow definition.
* @param holder the holder holding the flow definition to re-index
* @param namespace the namespace to index the new flow in
* @param oldFlowPath the flowPath that was previously assigned to given flow definition
*/
private void reindex(FlowDefinitionHolder holder, String namespace, String oldFlowPath) {
removeFlowDefinition(oldFlowPath);
index(holder, namespace);
}
/**
* Index given flow definition.
* @param holder the holder holding the flow definition to index
* @param namespaceName the namespace to index the flow definition in
*/
private void index(FlowDefinitionHolder holder, String namespaceName) {
Assert.hasText(holder.getFlowDefinitionId(), "The flow holder to index must return a non-blank flow id");
Map namespace = getNamespace(namespaceName);
namespace.put(holder.getFlowDefinitionId(), holder);
}
/**
* Returns the identified flow definition holder. Throws an exception if it cannot be found.
*/
private FlowDefinitionHolder getFlowDefinitionHolder(String flowPath) throws NoSuchFlowDefinitionException {
Map namespace = getNamespace(FlowPathUtils.extractFlowNamespace(flowPath));
FlowDefinitionHolder flowHolder = (FlowDefinitionHolder) namespace.get(FlowPathUtils.extractFlowId(flowPath));
if (flowHolder == null) {
throw new NoSuchFlowDefinitionException(flowPath, getFlowDefinitionPaths());
private FlowDefinitionHolder getFlowDefinitionHolder(String id) throws NoSuchFlowDefinitionException {
FlowDefinitionHolder holder = (FlowDefinitionHolder) flowDefinitions.get(id);
if (holder == null) {
throw new NoSuchFlowDefinitionException(id);
}
return flowHolder;
}
/**
* Returns the namespace map for a given namespace. Creates the map if it does not exist.
*/
private Map getNamespace(String namespace) {
if (!flowDefinitions.containsKey(namespace)) {
flowDefinitions.put(namespace, new TreeMap());
}
return (Map) flowDefinitions.get(namespace);
return holder;
}
public String toString() {

View File

@@ -1,86 +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.definition.registry;
/**
* A management interface for managing flow definition registries at runtime. Provides the ability to query the size and
* state of the registry, as well as refresh registered flow definitions at runtime.
* <p>
* Flow registries that implement this interface may be exposed for management over the JMX protocol. The following is
* an example of using Spring's JMX <code>MBeanExporter</code> to export a flow registry to an MBeanServer:
*
* <pre class="code">
* &lt;!-- Creates the registry of flow definitions for this application --&gt;
* &lt;bean name=&quot;flowRegistry&quot; class=&quot;org.springframework.webflow...XmlFlowRegistryFactoryBean&quot;&gt;
* &lt;property name=&quot;locations&quot; value=&quot;/WEB-INF/flow1.xml&quot;/&gt;
* &lt;/bean&gt;
*
* &lt;!-- Automatically exports the created flowRegistry as an MBean --&gt;
* &lt;bean id=&quot;mbeanExporter&quot; class=&quot;org.springframework.jmx.export.MBeanExporter&quot;&gt;
* &lt;property name=&quot;beans&quot;&gt;
* &lt;map&gt;
* &lt;entry key=&quot;spring-webflow:name=flowRegistry&quot; value-ref=&quot;flowRegistry&quot;/&gt;
* &lt;/map&gt;
* &lt;/property&gt;
* &lt;property name=&quot;assembler&quot;&gt;
* &lt;bean class=&quot;org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler&quot;&gt;
* &lt;property name=&quot;managedInterfaces&quot;
* value=&quot;org.springframework.webflow.definition.registry.FlowDefinitionRegistryMBean&quot;/&gt;
* &lt;/bean&gt;
* &lt;/property&gt;
* &lt;/bean&gt;
* </pre>
*
* With the above configuration, you may then use any JMX client (such as Sun's jConsole which ships with JDK 1.5) to
* refresh flow definitions at runtime.
*
* @author Keith Donald
*/
public interface FlowDefinitionRegistryMBean {
/**
* Returns the paths of the flow definitions registered in this registry.
* @return the flow definition paths
*/
public String[] getFlowDefinitionPaths();
/**
* Return the number of flow definitions registered in this registry.
* @return the flow definition count
*/
public int getFlowDefinitionCount();
/**
* Queries this registry to determine if a specific flow is contained within it.
* @param flowPath the flow definition path
* @return true if a flow definition is contained in this registry with the id provided
*/
public boolean containsFlowDefinition(String flowPath);
/**
* Refresh this flow definition registry, reloading all Flow definitions from their externalized representations.
*/
public void refresh() throws FlowDefinitionConstructionException;
/**
* Refresh the Flow definition in this registry with the <code>path</code> provided, reloading it from it's
* externalized representation.
* @param flowPath the path of the flow definition to refresh
* @throws NoSuchFlowDefinitionException if a flow with the id provided is not stored in this registry
*/
public void refresh(String flowPath) throws NoSuchFlowDefinitionException, FlowDefinitionConstructionException;
}

View File

@@ -1,162 +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.definition.registry;
import java.io.Serializable;
import org.springframework.core.io.Resource;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.webflow.core.collection.AttributeMap;
import org.springframework.webflow.core.collection.CollectionUtils;
/**
* A pointer to an externalized flow definition resource. Adds assigned identification information about the resource
* including the flow id and attributes.
*
* @see ExternalizedFlowDefinitionRegistrar
*
* @author Keith Donald
*/
public class FlowDefinitionResource implements Serializable {
/**
* The identifier to assign to the flow definition.
*/
private String id;
/**
* Attributes that can be used to affect flow construction.
*/
private AttributeMap attributes;
/**
* The externalized location of the flow definition resource.
*/
private Resource location;
/**
* Creates a new externalized flow definition resource. The flow id assigned will be the same name as the
* externalized resource's filename, excluding the extension.
* @param location the flow resource location
*/
public FlowDefinitionResource(Resource location) {
init(conventionalFlowId(location), location, null);
}
/**
* Creates a new externalized flow definition resource. The flow id assigned will be the same name as the
* externalized resource's filename, excluding the extension.
* @param location the flow resource location
* @param attributes flow definition attributes to be assigned
*/
public FlowDefinitionResource(Resource location, AttributeMap attributes) {
init(conventionalFlowId(location), location, attributes);
}
/**
* Creates a new externalized flow definition.
* @param id the flow id to be assigned
* @param location the flow resource location
*/
public FlowDefinitionResource(String id, Resource location) {
init(id, location, null);
}
/**
* Creates a new externalized flow definition.
* @param id the flow id to be assigned
* @param location the flow resource location
* @param attributes flow definition attributes to be assigned
*/
public FlowDefinitionResource(String id, Resource location, AttributeMap attributes) {
init(id, location, attributes);
}
/**
* Returns the identifier to assign to the flow definition.
*/
public String getId() {
return id;
}
/**
* Returns the externalized flow definition resource location.
*/
public Resource getLocation() {
return location;
}
/**
* Returns arbitrary flow definition attributes.
*/
public AttributeMap getAttributes() {
return attributes;
}
public boolean equals(Object o) {
if (!(o instanceof FlowDefinitionResource)) {
return false;
}
FlowDefinitionResource other = (FlowDefinitionResource) o;
return id.equals(other.id) && location.equals(other.location);
}
public int hashCode() {
return id.hashCode() + location.hashCode();
}
// internal helpers
/**
* Initialize this object.
*/
private void init(String id, Resource location, AttributeMap attributes) {
Assert.hasText(id, "The id of the externalized flow definition is required");
Assert.notNull(location, "The location of the externalized flow definition is required");
this.id = id;
this.location = location;
if (attributes != null) {
this.attributes = attributes;
} else {
this.attributes = CollectionUtils.EMPTY_ATTRIBUTE_MAP;
}
}
// public utilities
/**
* Returns the flow id assigned to the flow definition contained in given resource. By convention this will be the
* filename of the resource, excluding extension.
* @see FlowDefinitionResource#FlowDefinitionResource(Resource)
* @see FlowDefinitionResource#FlowDefinitionResource(Resource, AttributeMap)
* @since 1.0.1
*/
public static String conventionalFlowId(Resource location) {
String fileName = location.getFilename();
int extensionIndex = fileName.lastIndexOf('.');
if (extensionIndex != -1) {
return fileName.substring(0, extensionIndex);
} else {
return fileName;
}
}
public String toString() {
return new ToStringCreator(this).append("id", id).append("location", location).append("attributes", attributes)
.toString();
}
}

View File

@@ -1,55 +0,0 @@
package org.springframework.webflow.definition.registry;
import org.springframework.util.Assert;
/**
* Simple utility for working with flow paths. Only intended for internal use.
*
* @author Ben Hale
*/
class FlowPathUtils {
private static final String PATH_DELIMITER = "/";
/**
* Parses a flow path and returns the namespace part of the path.
* @param flowPath The path to parse
* @return The namespace part of the path
*/
public static String extractFlowNamespace(String flowPath) {
Assert.hasText(flowPath, "The flow path must not be empty or null");
int index = flowPath.lastIndexOf(PATH_DELIMITER);
if (index == -1) {
return "";
} else {
return flowPath.substring(0, index);
}
}
/**
* Parses a flow path and returns the id part of the path.
* @param flowPath The path to parse
* @return The id part of the path
*/
public static String extractFlowId(String flowPath) {
Assert.hasText(flowPath, "The flow path must not be empty or null");
int index = flowPath.lastIndexOf(PATH_DELIMITER);
if (index == -1) {
return flowPath;
} else {
return flowPath.substring(index + 1);
}
}
/**
* Creates a flow path based on given namespace and id.
* @param namespace The namespace of the path
* @param id The id of the path
* @return The complete flow path
*/
public static String buildFlowPath(String namespace, String id) {
Assert.notNull(namespace, "namespace must have some value");
Assert.hasText(id, "The id must not be empty or null");
return namespace + PATH_DELIMITER + id;
}
}

View File

@@ -15,7 +15,6 @@
*/
package org.springframework.webflow.definition.registry;
import org.springframework.core.style.StylerUtils;
import org.springframework.webflow.core.FlowException;
/**
@@ -29,23 +28,21 @@ public class NoSuchFlowDefinitionException extends FlowException {
/**
* The id of the flow definition that could not be located.
*/
private String flowId;
private String flowDefinitionId;
/**
* Creates an exception indicating a flow definition could not be found.
* @param flowId the flow id
* @param availableFlowIds all flow ids available to the locator generating this exception
* @param flowDefinitionId the flow definition id
*/
public NoSuchFlowDefinitionException(String flowId, String[] availableFlowIds) {
super("No such flow definition with id '" + flowId + "' found; the flows available are: "
+ StylerUtils.style(availableFlowIds));
this.flowId = flowId;
public NoSuchFlowDefinitionException(String flowDefinitionId) {
super("No flow definition '" + flowDefinitionId + "' found");
this.flowDefinitionId = flowDefinitionId;
}
/**
* Returns the id of the flow definition that could not be found.
*/
public String getFlowId() {
return flowId;
public String getFlowDefinitionId() {
return flowDefinitionId;
}
}

View File

@@ -1,51 +1,48 @@
/*
* 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.definition.registry;
import org.springframework.webflow.definition.FlowDefinition;
/**
* A simple flow definition holder that just holds a constant singleton reference to a flow definition.
*
* @author Keith Donald
*/
public final class StaticFlowDefinitionHolder implements FlowDefinitionHolder {
/**
* The held flow definition.
*/
private final FlowDefinition flowDefinition;
/**
* Creates the static flow definition holder.
* @param flowDefinition the flow to hold
*/
public StaticFlowDefinitionHolder(FlowDefinition flowDefinition) {
this.flowDefinition = flowDefinition;
}
public String getFlowDefinitionId() {
return flowDefinition.getId();
}
public FlowDefinition getFlowDefinition() {
return flowDefinition;
}
public void refresh() {
// nothing to do
}
package org.springframework.webflow.definition.registry;
import org.springframework.webflow.definition.FlowDefinition;
/**
* A simple flow definition holder that just holds a constant singleton reference to a flow definition.
* @author Keith Donald
*/
class StaticFlowDefinitionHolder implements FlowDefinitionHolder {
/**
* The held flow definition.
*/
private final FlowDefinition flowDefinition;
/**
* Creates the static flow definition holder.
* @param flowDefinition the flow to hold
*/
public StaticFlowDefinitionHolder(FlowDefinition flowDefinition) {
this.flowDefinition = flowDefinition;
}
public String getFlowDefinitionId() {
return flowDefinition.getId();
}
public FlowDefinition getFlowDefinition() throws FlowDefinitionConstructionException {
return flowDefinition;
}
public void refresh() throws FlowDefinitionConstructionException {
// nothing to do
}
public boolean equals(Object o) {
if (!(o instanceof StaticFlowDefinitionHolder)) {
return false;
}
StaticFlowDefinitionHolder other = (StaticFlowDefinitionHolder) o;
return flowDefinition.equals(other.flowDefinition);
}
public int hashCode() {
return flowDefinition.hashCode();
}
}

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.webflow.engine;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.webflow.execution.Action;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
@@ -30,8 +28,6 @@ import org.springframework.webflow.execution.RequestContext;
*/
public class ActionExecutor {
private static final Log logger = LogFactory.getLog(ActionExecutor.class);
/**
* Private constructor to avoid instantiation.
*/
@@ -48,14 +44,6 @@ public class ActionExecutor {
*/
public static Event execute(Action action, RequestContext context) throws ActionExecutionException {
try {
if (logger.isDebugEnabled()) {
if (context.getCurrentState() == null) {
logger.debug("Executing start " + action + " for flow '" + context.getActiveFlow().getId() + "'");
} else {
logger.debug("Executing " + action + " in state '" + context.getCurrentState().getId()
+ "' of flow '" + context.getActiveFlow().getId() + "'");
}
}
return action.execute(context);
} catch (ActionExecutionException e) {
throw e;

View File

@@ -23,7 +23,6 @@ import org.springframework.webflow.execution.Action;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.FlowExecutionException;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.ViewSelection;
/**
* A transitionable state that executes one or more actions when entered. When the action(s) are executed this state
@@ -123,7 +122,7 @@ public class ActionState extends TransitionableState {
/*
* Overrides getRequiredTransition(RequestContext) to throw a local NoMatchingActionResultTransitionException if a
* transition on the occurence of an action result event cannot be matched. Used to facilitate an action invocation
* transition on the occurrence of an action result event cannot be matched. Used to facilitate an action invocation
* chain. <p>Note that we cannot catch NoMatchingTransitionException since that could lead to unwanted situations
* where we're catching an exception that's generated by another state, e.g. because of a configuration error!
*/
@@ -136,7 +135,7 @@ public class ActionState extends TransitionableState {
}
/**
* Specialization of State's <code>doEnter</code> template method that executes behaviour specific to this state
* Specialization of State's <code>doEnter</code> template method that executes behavior specific to this state
* type in polymorphic fashion.
* <p>
* This implementation iterates over each configured <code>Action</code> instance and executes it. Execution
@@ -144,10 +143,9 @@ public class ActionState extends TransitionableState {
* context, or the set of all actions is exhausted.
* @param context the control context for the currently executing flow, used by this state to manipulate the flow
* execution
* @return a view selection signaling that control should be returned to the client and a view rendered
* @throws FlowExecutionException if an exception occurs in this state
*/
protected ViewSelection doEnter(RequestControlContext context) throws FlowExecutionException {
protected void doEnter(RequestControlContext context) throws FlowExecutionException {
int executionCount = 0;
String[] eventIds = new String[actionList.size()];
Iterator it = actionList.iterator();
@@ -157,8 +155,8 @@ public class ActionState extends TransitionableState {
if (event != null) {
eventIds[executionCount] = event.getId();
try {
// will check both local state transitions and global transitions
return context.signalEvent(event);
context.handleEvent(event);
return;
} catch (NoMatchingActionResultTransitionException e) {
if (logger.isDebugEnabled()) {
logger.debug("Action execution ["
@@ -207,7 +205,6 @@ public class ActionState extends TransitionableState {
/**
* Local "no transition found" exception used to report that an action result could not be mapped to a state
* transition.
*
* @author Keith Donald
* @author Erwin Vervaet
*/

View File

@@ -17,7 +17,6 @@ package org.springframework.webflow.engine;
import org.springframework.webflow.execution.FlowExecutionException;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.ViewSelection;
/**
* A simple transitionable state that when entered will execute the first transition whose matching criteria evaluates
@@ -40,17 +39,15 @@ public class DecisionState extends TransitionableState {
}
/**
* Specialization of State's <code>doEnter</code> template method that executes behaviour specific to this state
* Specialization of State's <code>doEnter</code> template method that executes behavior specific to this state
* type in polymorphic fashion.
* <p>
* Simply looks up the first transition that matches the state of the context and executes it.
* @param context the control context for the currently executing flow, used by this state to manipulate the flow
* execution
* @return a view selection containing model and view information needed to render the results of the state
* execution
* @throws FlowExecutionException if an exception occurs in this state
*/
protected ViewSelection doEnter(RequestControlContext context) throws FlowExecutionException {
return getRequiredTransition(context).execute(this, context);
protected void doEnter(RequestControlContext context) throws FlowExecutionException {
getRequiredTransition(context).execute(this, context);
}
}

View File

@@ -16,35 +16,28 @@
package org.springframework.webflow.engine;
import org.springframework.binding.mapping.AttributeMapper;
import org.springframework.binding.mapping.MappingContext;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.webflow.core.collection.LocalAttributeMap;
import org.springframework.webflow.execution.Action;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.FlowExecutionException;
import org.springframework.webflow.execution.FlowSession;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.ViewSelection;
/**
* A state that ends a flow when entered. More specifically, this state ends the active flow session of the active flow
* execution associated with the current request context.
* A state that ends a flow when entered. This state ends the active flow session of an ongoing flow execution.
* <p>
* If the ended session is the "root flow session" the entire flow execution ends, signaling the end of a logical
* conversation.
* <p>
* If the terminated session was acting as a subflow the flow execution continues and control is returned to the parent
* flow session. In that case, this state returns an ending result event the resuming parent flow is expected to respond
* to.
* If the terminated session was acting as a subflow, the flow execution continues and control is returned to the parent
* flow session. In that case, this state returns an ending result event the resuming parent flow responds to.
* <p>
* An end state may optionally be configured with the name of a view to render when entered. This view will be rendered
* if the end state terminates the entire flow execution as a kind of flow ending "confirmation page".
* <p>
* Note: if no <code>viewName</code> property is specified <b>and</b> this end state terminates the entire flow
* execution it is expected that some action has already written the response (or else a blank response will result). On
* the other hand, if no <code>viewName</code> is specified <b>and</b> this end state relinquishes control back to a
* parent flow, view selection responsibility falls on the parent flow.
* An end state may be configured with a renderer to render a final response. This renderer will be invoked if the end
* state terminates the entire flow execution.
*
* @see org.springframework.webflow.engine.ViewSelector
* @see org.springframework.webflow.engine.SubflowState
*
* @author Keith Donald
@@ -54,14 +47,14 @@ import org.springframework.webflow.execution.ViewSelection;
public class EndState extends State {
/**
* The optional view selector that will select a view to render if this end state terminates a root flow session.
* The renderer that will render the final response when a flow execution terminates.
*/
private ViewSelector viewSelector = NullViewSelector.INSTANCE;
private Action finalResponseAction = new NullFinalResponseAction();
/**
* Attribute mapper for mapping output attributes exposed by this end state when it is entered.
* The attribute mapper for mapping output attributes exposed by this end state when it is entered.
*/
private AttributeMapper outputMapper;
private AttributeMapper outputMapper = new NoOutputMapper();
/**
* Create a new end state with no associated view.
@@ -69,7 +62,7 @@ public class EndState extends State {
* @param id the state identifier (must be unique to the flow)
* @throws IllegalArgumentException when this state cannot be added to given flow, e.g. because the id is not unique
* @see State#State(Flow, String)
* @see #setViewSelector(ViewSelector)
* @see #setFinalResponseAction(Action)
* @see #setOutputMapper(AttributeMapper)
*/
public EndState(Flow flow, String id) throws IllegalArgumentException {
@@ -77,58 +70,42 @@ public class EndState extends State {
}
/**
* Returns the strategy used to select the view to render in this end state if it terminates a root flow.
* Sets the renderer that will render the final flow execution response.
*/
public ViewSelector getViewSelector() {
return viewSelector;
}
/**
* Sets the strategy used to select the view to render when this end state is entered and terminates a root flow.
*/
public void setViewSelector(ViewSelector viewSelector) {
Assert.notNull(viewSelector, "The view selector is required");
this.viewSelector = viewSelector;
}
/**
* Returns the configured attribute mapper for mapping output attributes exposed by this end state when it is
* entered.
*/
public AttributeMapper getOutputMapper() {
return outputMapper;
public void setFinalResponseAction(Action finalResponseAction) {
Assert.notNull(finalResponseAction, "The final response action is required");
this.finalResponseAction = finalResponseAction;
}
/**
* Sets the attribute mapper to use for mapping output attributes exposed by this end state when it is entered.
*/
public void setOutputMapper(AttributeMapper outputMapper) {
Assert.notNull(outputMapper, "The flow session output mapper is required");
this.outputMapper = outputMapper;
}
/**
* Specialization of State's <code>doEnter</code> template method that executes behaviour specific to this state
* Specialization of State's <code>doEnter</code> template method that executes behavior specific to this state
* type in polymorphic fashion.
* <p>
* This implementation pops the top (active) flow session off the execution stack, ending it, and resumes control in
* the parent flow (if neccessary). If the ended session is the root flow, a {@link ViewSelection} is returned.
* the parent flow (if necessary). If the ended session is the root flow, a final response is rendered.
* @param context the control context for the currently executing flow, used by this state to manipulate the flow
* execution
* @return a view selection signaling that control should be returned to the client and a view rendered
* @throws FlowExecutionException if an exception occurs in this state
*/
protected ViewSelection doEnter(RequestControlContext context) throws FlowExecutionException {
protected void doEnter(final RequestControlContext context) throws FlowExecutionException {
FlowSession activeSession = context.getFlowExecutionContext().getActiveSession();
if (activeSession.isRoot()) {
// entire flow execution is ending, return ending view if applicable
ViewSelection selectedView = viewSelector.makeEntrySelection(context);
// entire flow execution is ending; issue the final response
ActionExecutor.execute(finalResponseAction, context);
context.endActiveFlowSession(createSessionOutput(context));
return selectedView;
} else {
// there is a parent flow that will resume (this flow is a subflow)
LocalAttributeMap sessionOutput = createSessionOutput(context);
context.endActiveFlowSession(sessionOutput);
return context.signalEvent(new Event(this, getId(), sessionOutput));
context.handleEvent(new Event(this, getId(), sessionOutput));
}
}
@@ -138,13 +115,38 @@ public class EndState extends State {
*/
protected LocalAttributeMap createSessionOutput(RequestContext context) {
LocalAttributeMap outputMap = new LocalAttributeMap();
if (outputMapper != null) {
outputMapper.map(context, outputMap, null);
}
outputMapper.map(context, outputMap, null);
return outputMap;
}
protected void appendToString(ToStringCreator creator) {
creator.append("viewSelector", viewSelector).append("outputMapper", outputMapper);
creator.append("finalResponseAction", finalResponseAction).append("outputMapper", outputMapper);
}
/**
* Renders no response. The default implementation.
*/
private class NullFinalResponseAction implements Action {
public Event execute(RequestContext context) {
logger.debug("Not issuing a final response");
return new Event(this, "success");
}
public String toString() {
return "none";
}
}
/**
* Maps no output attributes. The default implementation.
*/
private class NoOutputMapper implements AttributeMapper {
public void map(Object source, Object target, MappingContext context) {
logger.debug("No output attributes mapped");
}
public String toString() {
return "none";
}
}
}

View File

@@ -21,17 +21,20 @@ import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.binding.mapping.AttributeMapper;
import org.springframework.binding.mapping.MappingContext;
import org.springframework.core.CollectionFactory;
import org.springframework.core.style.StylerUtils;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.webflow.context.ExternalContext;
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.definition.FlowDefinition;
import org.springframework.webflow.definition.StateDefinition;
import org.springframework.webflow.execution.FlowExecutionException;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.ViewSelection;
/**
* A single flow definition. A Flow definition is a reusable, self-contained controller module that provides the blue
@@ -45,7 +48,7 @@ import org.springframework.webflow.execution.ViewSelection;
* <p>
* Especially in Intranet applications there are often "controlled navigations" where the user is not free to do what he
* or she wants but must follow the guidelines provided by the system to complete a process that is transactional in
* nature (the quinessential example would be a 'checkout' flow of a shopping cart application). This is a typical use
* nature (the quintessential example would be a 'checkout' flow of a shopping cart application). This is a typical use
* case appropriate to model as a flow.
* <p>
* Structurally a Flow is composed of a set of states. A {@link State} is a point in a flow where a behavior is
@@ -55,7 +58,7 @@ import org.springframework.webflow.execution.ViewSelection;
* Each {@link TransitionableState} type has one or more transitions that when executed move a flow to another state.
* These transitions define the supported paths through the flow.
* <p>
* A state transition is triggered by the occurence of an event. An event is something that happens the flow should
* A state transition is triggered by the occurrence of an event. An event is something that happens the flow should
* respond to, for example a user input event like ("submit") or an action execution result event like ("success"). When
* an event occurs in a state of a Flow that event drives a state transition that decides what to do next.
* <p>
@@ -76,14 +79,15 @@ import org.springframework.webflow.execution.ViewSelection;
* the ability to design reusable, high-level controller modules that may be executed in <i>any</i> environment.
* <p>
* Note: flows are singleton definition objects so they should be thread-safe. You can think a flow definition as
* analagous somewhat to a Java class, defining all the behavior of an application module. The core behaviors
* {@link #start(RequestControlContext, MutableAttributeMap) start}, {@link #onEvent(RequestControlContext) on event},
* and {@link #end(RequestControlContext, MutableAttributeMap) end} each accept a {@link RequestContext request context}
* that allows for this flow to access execution state in a thread safe manner. A flow execution is what models a
* running instance of this flow definition, somewhat analgous to a java object that is an instance of a class.
* analogous to a Java class, defining all the behavior of an application module. The core behaviors
* {@link #start(RequestControlContext, MutableAttributeMap) start}, {@link #resume(RequestControlContext)},
* {@link #handleEvent(RequestControlContext) on event}, {@link #end(RequestControlContext, MutableAttributeMap) end},
* and {@link #handleException(FlowExecutionException, RequestControlContext)}. Each method accepts a
* {@link RequestContext request context} that allows for this flow to access execution state in a thread safe manner. A
* flow execution is what models a running instance of this flow definition, somewhat analogous to a java object that is
* an instance of a class.
*
* @see org.springframework.webflow.engine.State
* @see org.springframework.webflow.engine.TransitionableState
* @see org.springframework.webflow.engine.ActionState
* @see org.springframework.webflow.engine.ViewState
* @see org.springframework.webflow.engine.SubflowState
@@ -126,7 +130,7 @@ public class Flow extends AnnotatedObject implements FlowDefinition {
/**
* The mapper to map flow input attributes.
*/
private AttributeMapper inputMapper;
private AttributeMapper inputMapper = new NoInputMapper();
/**
* The list of actions to execute when this flow starts.
@@ -149,24 +153,34 @@ public class Flow extends AnnotatedObject implements FlowDefinition {
/**
* The mapper to map flow output attributes.
*/
private AttributeMapper outputMapper;
private AttributeMapper outputMapper = new NoOutputMapper();
/**
* The set of exception handlers for this flow.
*/
private FlowExecutionExceptionHandlerSet exceptionHandlerSet = new FlowExecutionExceptionHandlerSet();
/**
* The set of inline flows contained by this flow.
*/
private Set inlineFlows = CollectionFactory.createLinkedSetIfPossible(3);
/**
* Construct a new flow definition with the given id. The id should be unique among all flows.
* @param id the flow identifier
*/
public Flow(String id) {
setId(id);
Assert.hasText(id, "This flow must be uniquely identified");
this.id = id;
}
// convenient static factory methods
/**
* Create a new flow with the given id and attributes.
* @param id the flow id
* @param attributes the attributes
* @return the flow
*/
public static Flow create(String id, AttributeMap attributes) {
Flow flow = new Flow(id);
flow.getAttributeMap().putAll(attributes);
return flow;
}
// implementing FlowDefinition
@@ -187,14 +201,6 @@ public class Flow extends AnnotatedObject implements FlowDefinition {
return getStateInstance(stateId);
}
/**
* Set the unique id of this flow.
*/
protected void setId(String id) {
Assert.hasText(id, "This flow must have a unique, non-blank identifier");
this.id = id;
}
/**
* Add given state definition to this flow definition. Marked protected, as this method is to be called by the
* (privileged) state definition classes themselves during state construction as part of a FlowBuilder invocation.
@@ -358,6 +364,7 @@ public class Flow extends AnnotatedObject implements FlowDefinition {
* @param inputMapper the input mapper
*/
public void setInputMapper(AttributeMapper inputMapper) {
Assert.notNull(inputMapper, "The input mapper cannot be null");
this.inputMapper = inputMapper;
}
@@ -392,6 +399,7 @@ public class Flow extends AnnotatedObject implements FlowDefinition {
* @param outputMapper the output mapper
*/
public void setOutputMapper(AttributeMapper outputMapper) {
Assert.notNull(outputMapper, "The output mapper cannot be null");
this.outputMapper = outputMapper;
}
@@ -406,74 +414,6 @@ public class Flow extends AnnotatedObject implements FlowDefinition {
return exceptionHandlerSet;
}
/**
* Adds an inline flow to this flow.
* @param flow the inline flow to add
*/
public void addInlineFlow(Flow flow) {
inlineFlows.add(flow);
}
/**
* Returns the list of inline flow ids.
* @return a string array of inline flow identifiers
*/
public String[] getInlineFlowIds() {
String[] flowIds = new String[getInlineFlowCount()];
int i = 0;
Iterator it = inlineFlows.iterator();
while (it.hasNext()) {
flowIds[i++] = ((Flow) it.next()).getId();
}
return flowIds;
}
/**
* Returns the list of inline flows.
* @return the list of inline flows
*/
public Flow[] getInlineFlows() {
return (Flow[]) inlineFlows.toArray(new Flow[inlineFlows.size()]);
}
/**
* Returns the count of registered inline flows.
* @return the count
*/
public int getInlineFlowCount() {
return inlineFlows.size();
}
/**
* Tests if this flow contains an in-line flow with the specified id.
* @param id the inline flow id
* @return true if this flow contains a inline flow with that id, false otherwise
*/
public boolean containsInlineFlow(String id) {
return getInlineFlow(id) != null;
}
/**
* Returns the inline flow with the provided id, or <code>null</code> if no such inline flow exists.
* @param id the inline flow id
* @return the inline flow
* @throws IllegalArgumentException when an invalid flow id is provided
*/
public Flow getInlineFlow(String id) throws IllegalArgumentException {
if (!StringUtils.hasText(id)) {
throw new IllegalArgumentException(
"The specified inline flowId is invalid: flow identifiers must be non-blank");
}
Iterator it = inlineFlows.iterator();
while (it.hasNext()) {
Flow flow = (Flow) it.next();
if (flow.getId().equals(id)) {
return flow;
}
}
return null;
}
/**
* Returns the set of transitions eligible for execution by this flow if no state-level transition is matched. The
* returned set is mutable.
@@ -499,12 +439,21 @@ public class Flow extends AnnotatedObject implements FlowDefinition {
// behavioral code, could be overridden in subclasses
/**
* Factory method that creates the initial input map to pass to this flow when a new execution of this flow is
* started. Allows this flow to assemble the input map from data in the external context representing the calling
* environment.
*/
public MutableAttributeMap createExecutionInputMap(ExternalContext context) {
return new LocalAttributeMap();
}
/**
* Start a new session for this flow in its start state. This boils down to the following:
* <ol>
* <li>Create (setup) all registered flow variables ({@link #addVariable(FlowVariable)}) in flow scope.</li>
* <li>Map provided input data into the flow execution control context. Typically data will be mapped into flow
* scope using the registered input mapper ({@link #setInputMapper(AttributeMapper)}).</li>
* <li>Map provided input data into the flow. Typically data will be mapped into flow scope using the registered
* input mapper ({@link #setInputMapper(AttributeMapper)}).</li>
* <li>Execute all registered start actions ({@link #getStartActionList()}).</li>
* <li>Enter the configured start state ({@link #setStartState(State)})</li>
* </ol>
@@ -512,31 +461,36 @@ public class Flow extends AnnotatedObject implements FlowDefinition {
* @param input eligible input into the session
* @throws FlowExecutionException when an exception occurs starting the flow
*/
public ViewSelection start(RequestControlContext context, MutableAttributeMap input) throws FlowExecutionException {
public void start(RequestControlContext context, MutableAttributeMap input) throws FlowExecutionException {
assertStartStateSet();
createVariables(context);
if (inputMapper != null) {
inputMapper.map(input, context, null);
}
inputMapper.map(input, context, null);
startActionList.execute(context);
return startState.enter(context);
startState.enter(context);
}
/**
* Inform this flow definition that an event was signaled in the current state of an active flow execution. The
* signaled event is the last event available in given request context ({@link RequestContext#getLastEvent()}).
* Resume a paused session for this flow in its current view state.
* @param context the flow execution control context
* @return the selected view
* @throws FlowExecutionException when an exception occurs processing the event
* @throws FlowExecutionException when an exception occurs during the resume operation
*/
public ViewSelection onEvent(RequestControlContext context) throws FlowExecutionException {
public void resume(RequestControlContext context) throws FlowExecutionException {
getCurrentViewState(context).resume(context);
}
/**
* Handle the last event that occurred against an active session of this flow.
* @param context the flow execution control context
*/
public void handleEvent(RequestControlContext context) {
TransitionableState currentState = getCurrentTransitionableState(context);
try {
return currentState.onEvent(context);
currentState.handleEvent(context);
} catch (NoMatchingTransitionException e) {
// try the flow level transition set for a match
Transition transition = globalTransitionSet.getTransition(context);
if (transition != null) {
return transition.execute(currentState, context);
transition.execute(currentState, context);
} else {
// no matching global transition => let the original exception
// propagate
@@ -559,25 +513,28 @@ public class Flow extends AnnotatedObject implements FlowDefinition {
*/
public void end(RequestControlContext context, MutableAttributeMap output) throws FlowExecutionException {
endActionList.execute(context);
if (outputMapper != null) {
outputMapper.map(context, output, null);
}
outputMapper.map(context, output, null);
}
/**
* Handle an exception that occured during an execution of this flow.
* @param exception the exception that occured
* Handle an exception that occurred during an execution of this flow.
* @param exception the exception that occurred
* @param context the flow execution control context
* @return the selected error view, or <code>null</code> if no handler matched or returned a non-null view
* selection
*/
public ViewSelection handleException(FlowExecutionException exception, RequestControlContext context)
public boolean handleException(FlowExecutionException exception, RequestControlContext context)
throws FlowExecutionException {
return getExceptionHandlerSet().handleException(exception, context);
}
// internal helpers
private void assertStartStateSet() {
if (startState == null) {
throw new IllegalStateException("Unable to start flow '" + id
+ "'; the start state is not set -- flow builder configuration error?");
}
}
/**
* Create (setup) all known flow variables in flow scope.
*/
@@ -592,6 +549,18 @@ public class Flow extends AnnotatedObject implements FlowDefinition {
}
}
/**
* Returns the current state and makes sure it is a view state.
*/
private ViewState getCurrentViewState(RequestControlContext context) {
State currentState = (State) context.getCurrentState();
if (!(currentState instanceof ViewState)) {
throw new IllegalStateException("You can only resume paused view states, and state "
+ context.getCurrentState() + " is not a view state - programmer error");
}
return (ViewState) currentState;
}
/**
* Returns the current state and makes sure it is transitionable.
*/
@@ -604,11 +573,39 @@ public class Flow extends AnnotatedObject implements FlowDefinition {
return (TransitionableState) currentState;
}
/**
* Maps no input attributes. The default implementation.
*/
private class NoInputMapper implements AttributeMapper {
public void map(Object source, Object target, MappingContext context) {
logger.debug("No input attributes mapped");
}
public String toString() {
return "none";
}
}
/**
* Maps no input attributes. The default implementation.
*/
private class NoOutputMapper implements AttributeMapper {
public void map(Object source, Object target, MappingContext context) {
logger.debug("No output attributes mapped");
}
public String toString() {
return "none";
}
}
public String toString() {
return new ToStringCreator(this).append("id", id).append("states", states).append("startState", startState)
.append("variables", variables).append("inputMapper", inputMapper).append("startActionList",
startActionList).append("exceptionHandlerSet", exceptionHandlerSet).append(
"globalTransitionSet", globalTransitionSet).append("endActionList", endActionList).append(
"outputMapper", outputMapper).append("inlineFlows", inlineFlows).toString();
"outputMapper", outputMapper).toString();
}
}

View File

@@ -16,7 +16,6 @@
package org.springframework.webflow.engine;
import org.springframework.webflow.execution.FlowExecutionException;
import org.springframework.webflow.execution.ViewSelection;
/**
* A strategy for handling an exception that occurs at runtime during the execution of a flow definition.
@@ -27,18 +26,16 @@ public interface FlowExecutionExceptionHandler {
/**
* Can this handler handle the given exception?
* @param exception the exception that occured
* @param exception the exception that occurred
* @return true if yes, false if no
*/
public boolean handles(FlowExecutionException exception);
public boolean canHandle(FlowExecutionException exception);
/**
* Handle the exception in the context of the current request, optionally making an error view selection that should
* be rendered.
* @param exception the exception that occured
* @param exception the exception that occurred
* @param context the execution control context for this request
* @return the selected error view that should be displayed (may be null if the handler chooses not to select a
* view, in which case other exception handlers may be given a chance to handle the exception)
*/
public ViewSelection handle(FlowExecutionException exception, RequestControlContext context);
public void handle(FlowExecutionException exception, RequestControlContext context);
}

View File

@@ -22,7 +22,6 @@ import java.util.List;
import org.springframework.core.style.StylerUtils;
import org.springframework.webflow.core.collection.CollectionUtils;
import org.springframework.webflow.execution.FlowExecutionException;
import org.springframework.webflow.execution.ViewSelection;
/**
* A typed set of state exception handlers, mainly for use internally by artifacts that can apply state exception
@@ -98,28 +97,24 @@ public class FlowExecutionExceptionHandlerSet {
}
/**
* Handle an exception that occured during the context of the current flow execution request.
* Handle an exception that occurred during the context of the current flow execution request.
* <p>
* This implementation iterates over the ordered set of exception handler objects, delegating to each handler in the
* set until one handles the exception that occured and selects a non-null error view.
* @param exception the exception that occured
* set until one handles the exception that occurred.
* @param exception the exception that occurred
* @param context the flow execution control context
* @return the selected error view, or <code>null</code> if no handler matched or returned a non-null view
* selection
* @return true if the exception was handled
*/
public ViewSelection handleException(FlowExecutionException exception, RequestControlContext context) {
public boolean handleException(FlowExecutionException exception, RequestControlContext context) {
Iterator it = exceptionHandlers.iterator();
while (it.hasNext()) {
FlowExecutionExceptionHandler handler = (FlowExecutionExceptionHandler) it.next();
if (handler.handles(exception)) {
ViewSelection result = handler.handle(exception, context);
if (result != null) {
return result;
}
// else continue with next handler
if (handler.canHandle(exception)) {
handler.handle(exception, context);
return true;
}
}
return null;
return false;
}
public String toString() {

View File

@@ -29,7 +29,7 @@ import org.springframework.webflow.execution.FlowExecutionException;
public class NoMatchingTransitionException extends FlowExecutionException {
/**
* The event that occured that could not be matched to a Transition.
* The event that occurred that could not be matched to a Transition.
*/
private Event event;

View File

@@ -1,66 +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 java.io.ObjectStreamException;
import java.io.Serializable;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.ViewSelection;
/**
* Makes a null view selection, indicating no response should be issued.
*
* @see org.springframework.webflow.execution.ViewSelection#NULL_VIEW
*
* @author Keith Donald
*/
public final class NullViewSelector implements ViewSelector, Serializable {
/*
* Implementation note: not located in webflow.execution.support package to avoid a cyclic dependency between
* webflow.execution and webflow.execution.support.
*/
/**
* The shared singleton {@link NullViewSelector} instance.
*/
public static final ViewSelector INSTANCE = new NullViewSelector();
/**
* Private constructor since this is a singleton.
*/
private NullViewSelector() {
}
public boolean isEntrySelectionRenderable(RequestContext context) {
return true;
}
public ViewSelection makeEntrySelection(RequestContext context) {
return ViewSelection.NULL_VIEW;
}
public ViewSelection makeRefreshSelection(RequestContext context) {
return makeEntrySelection(context);
}
// resolve the singleton instance
private Object readResolve() throws ObjectStreamException {
return INSTANCE;
}
}

View File

@@ -19,17 +19,16 @@ import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.FlowExecutionContext;
import org.springframework.webflow.execution.FlowExecutionException;
import org.springframework.webflow.execution.FlowExecutionKey;
import org.springframework.webflow.execution.FlowSession;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.ViewSelection;
/**
* Mutable control interface used to manipulate an ongoing flow execution in the context of one client request.
* Primarily used internally by the various flow artifacts when they are invoked.
* <p>
* This interface acts as a facade for core definition constructs such as the central <code>Flow</code> and
* <code>State</code> classes, abstracting away details about the runtime execution machine defined in the
* {@link org.springframework.webflow.engine.impl execution engine implementation} package.
* <code>State</code> classes, abstracting away details about the runtime execution machine.
* <p>
* Note this type is not the same as the {@link FlowExecutionContext}. Objects of this type are <i>request specific</i>:
* they provide a control interface for manipulating exactly one flow execution locally from exactly one request. A
@@ -47,12 +46,12 @@ import org.springframework.webflow.execution.ViewSelection;
public interface RequestControlContext extends RequestContext {
/**
* Record the last event signaled in the executing flow. This method will be called as part of signaling an event in
* a flow to indicate the 'lastEvent' that was signaled.
* @param lastEvent the last event signaled
* @see Flow#onEvent(RequestControlContext)
* Record the current state that has entered in the executing flow. This method will be called as part of entering a
* new state by the State type itself.
* @param state the current state
* @see State#enter(RequestControlContext)
*/
public void setLastEvent(Event lastEvent);
public void setCurrentState(State state);
/**
* Record the last transition that executed in the executing flow. This method will be called as part of executing a
@@ -63,12 +62,10 @@ public interface RequestControlContext extends RequestContext {
public void setLastTransition(Transition lastTransition);
/**
* Record the current state that has entered in the executing flow. This method will be called as part of entering a
* new state by the State type itself.
* @param state the current state
* @see State#enter(RequestControlContext)
* Assign the ongoing flow execution its flow execution key. This method will be called before a state is about to
* render a view and pause the flow execution.
*/
public void setCurrentState(State state);
public FlowExecutionKey assignFlowExecutionKey();
/**
* Spawn a new flow session and activate it in the currently executing flow. Also transitions the spawned flow to
@@ -77,26 +74,22 @@ public interface RequestControlContext extends RequestContext {
* This will start a new flow session in the current flow execution, which is already active.
* @param flow the flow to start, its <code>start()</code> method will be called
* @param input initial contents of the newly created flow session (may be <code>null</code>, e.g. empty)
* @return the selected starting view, which returns control to the client and requests that a view be rendered with
* model data
* @throws FlowExecutionException if an exception was thrown within a state of the flow during execution of this
* start operation
* @see Flow#start(RequestControlContext, MutableAttributeMap)
*/
public ViewSelection start(Flow flow, MutableAttributeMap input) throws FlowExecutionException;
public void start(Flow flow, MutableAttributeMap input) throws FlowExecutionException;
/**
* Signals the occurence of an event in the current state of this flow execution request context. This method should
* be called by clients that report internal event occurences, such as action states. The <code>onEvent()</code>
* method of the flow involved in the flow execution will be called.
* @param event the event that occured
* @return the next selected view, which returns control to the client and requests that a view be rendered with
* model data
* Signals the occurrence of an event in the current state of this flow execution request context. This method
* should be called by clients that report internal event occurrences, such as action states. The
* <code>onEvent()</code> method of the flow involved in the flow execution will be called.
* @param event the event that occurred
* @throws FlowExecutionException if an exception was thrown within a state of the flow during execution of this
* signalEvent operation
* @see Flow#onEvent(RequestControlContext)
* @see Flow#handleEvent(RequestControlContext)
*/
public ViewSelection signalEvent(Event event) throws FlowExecutionException;
public void handleEvent(Event event) throws FlowExecutionException;
/**
* End the active flow session of the current flow execution. This method should be called by clients that terminate
@@ -113,9 +106,21 @@ public interface RequestControlContext extends RequestContext {
* Execute this transition out of the current source state. Allows for privileged execution of an arbitrary
* transition.
* @param transition the transition
* @return a new view selection
* @see Transition#execute(State, RequestControlContext)
*/
public ViewSelection execute(Transition transition);
public void execute(Transition transition);
/**
* Returns true if the 'always redirect pause' flow execution attribute is set to true, false otherwise.
* @return true or false
*/
public boolean getAlwaysRedirectOnPause();
/**
* Request that a redirect be sent to this flow execution after the current request has processed. The current flow
* execution must have its key assigned for this operation to be supported.
* @throws IllegalStateException if the flow execution has not yet had its key assigned
*/
public void sendFlowExecutionRedirect() throws IllegalStateException;
}

View File

@@ -22,7 +22,6 @@ import org.springframework.util.Assert;
import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.webflow.definition.StateDefinition;
import org.springframework.webflow.execution.FlowExecutionException;
import org.springframework.webflow.execution.ViewSelection;
/**
* A point in a flow where something happens. What happens is determined by a state's type. Standard types of states
@@ -32,8 +31,8 @@ import org.springframework.webflow.execution.ViewSelection;
* configuration information needed for a specific kind of state.
* <p>
* Subclasses should implement the <code>doEnter</code> method to execute the processing that should occur when this
* state is entered, acting on its configuration information. The ability to plugin custom state types that execute
* different behaviour polymorphically is the classic GoF state pattern.
* state is entered, acting on its configuration information. The ability to plug-in custom state types that execute
* different behaviors is the classic GoF state pattern.
* <p>
* Equality: Two states are equal if they have the same id and are part of the same flow.
*
@@ -178,53 +177,47 @@ public abstract class State extends AnnotatedObject implements StateDefinition {
* the entry actions.
* @param context the control context for the currently executing flow, used by this state to manipulate the flow
* execution
* @return a view selection containing model and view information needed to render the results of the state
* processing
* @throws FlowExecutionException if an exception occurs in this state
*/
public final ViewSelection enter(RequestControlContext context) throws FlowExecutionException {
public final void enter(RequestControlContext context) throws FlowExecutionException {
if (logger.isDebugEnabled()) {
logger.debug("Entering state '" + getId() + "' of flow '" + getFlow().getId() + "'");
}
context.setCurrentState(this);
entryActionList.execute(context);
return doEnter(context);
doEnter(context);
}
/**
* Hook method to execute custom behaviour as a result of entering this state. By implementing this method
* subclasses specialize the behaviour of the state.
* Hook method to execute custom behavior as a result of entering this state. By implementing this method subclasses
* specialize the behavior of the state.
* @param context the control context for the currently executing flow, used by this state to manipulate the flow
* execution
* @return a view selection containing model and view information needed to render the results of the state
* processing
* @throws FlowExecutionException if an exception occurs in this state
*/
protected abstract ViewSelection doEnter(RequestControlContext context) throws FlowExecutionException;
protected abstract void doEnter(RequestControlContext context) throws FlowExecutionException;
/**
* Handle an exception that occured in this state during the context of the current flow execution request.
* @param exception the exception that occured
* Handle an exception that occurred in this state during the context of the current flow execution request.
* @param exception the exception that occurred
* @param context the flow execution control context
* @return the selected error view, or <code>null</code> if no handler matched or returned a non-null view
* selection
*/
public ViewSelection handleException(FlowExecutionException exception, RequestControlContext context) {
public boolean handleException(FlowExecutionException exception, RequestControlContext context) {
return getExceptionHandlerSet().handleException(exception, context);
}
public String toString() {
String flowName = (flow == null ? "<not set>" : flow.getId());
ToStringCreator creator = new ToStringCreator(this).append("id", getId()).append("flow", flowName).append(
ToStringCreator creator = new ToStringCreator(this).append("id", getId()).append("flow", flow.getId()).append(
"entryActionList", entryActionList).append("exceptionHandlerSet", exceptionHandlerSet);
appendToString(creator);
return creator.toString();
}
/**
* Subclasses may override this hook method to stringify their internal state. This default implementation does
* nothing.
* @param creator the toString creator, to stringify properties
* Subclasses may override this hook method to print their internal state to a string. This default implementation
* does nothing.
* @param creator the toString creator, to print properties to string
* @see #toString()
*/
protected void appendToString(ToStringCreator creator) {
}

View File

@@ -18,10 +18,10 @@ package org.springframework.webflow.engine;
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;
import org.springframework.webflow.execution.ViewSelection;
/**
* A transitionable state that spawns a subflow when executed. When the subflow this state spawns ends, the ending
@@ -41,14 +41,14 @@ import org.springframework.webflow.execution.ViewSelection;
public class SubflowState extends TransitionableState {
/**
* The subflow that should be spawned when this subflow state is entered.
* The subflow that should be spawned when this subflow state is entered. TODO - late binding
*/
private Flow subflow;
/**
* The attribute mapper that should map attributes from the parent flow down to the spawned subflow and visa versa.
*/
private FlowAttributeMapper attributeMapper;
private FlowAttributeMapper attributeMapper = new NoAttributeMapper();
/**
* Create a new subflow state.
@@ -64,15 +64,7 @@ public class SubflowState extends TransitionableState {
}
/**
* Returns the subflow spawned by this state.
*/
public Flow getSubflow() {
return subflow;
}
/**
* Set the subflow that will be spawned by this state.
* @param subflow the subflow to spawn
* Set the subflow this state will call.
*/
private void setSubflow(Flow subflow) {
Assert.notNull(subflow, "A subflow state must have a subflow; the subflow is required");
@@ -80,17 +72,10 @@ public class SubflowState extends TransitionableState {
}
/**
* Returns the attribute mapper used to map data between the parent and child flow, or null if no mapping is needed.
*/
public FlowAttributeMapper getAttributeMapper() {
return attributeMapper;
}
/**
* Set the attribute mapper used to map model data between the parent and child flow. Can be null if no mapping is
* needed.
* 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;
}
@@ -101,70 +86,41 @@ public class SubflowState extends TransitionableState {
* Entering this state, creates the subflow input map and spawns the subflow in the current flow execution.
* @param context the control context for the currently executing flow, used by this state to manipulate the flow
* execution
* @return a view selection containing model and view information needed to render the results of the state
* execution
* @throws FlowExecutionException if an exception occurs in this state
*/
protected ViewSelection doEnter(RequestControlContext context) throws FlowExecutionException {
protected void doEnter(RequestControlContext context) throws FlowExecutionException {
if (logger.isDebugEnabled()) {
logger.debug("Spawning subflow '" + getSubflow().getId() + "' within flow '" + getFlow().getId() + "'");
}
return context.start(getSubflow(), createSubflowInput(context));
}
/**
* Create the input data map for the spawned subflow session. The returned map will be passed to
* {@link Flow#start(RequestControlContext, MutableAttributeMap)}.
*/
protected MutableAttributeMap createSubflowInput(RequestContext context) {
if (getAttributeMapper() != null) {
if (logger.isDebugEnabled()) {
logger.debug("Messaging the configured attribute mapper to map attributes "
+ "down to the spawned subflow for access within the subflow");
}
return getAttributeMapper().createFlowInput(context);
} else {
if (logger.isDebugEnabled()) {
logger.debug("No attribute mapper configured for this subflow state '" + getId()
+ "' -- As a result, no attributes will be passed to the spawned subflow '" + subflow.getId()
+ "'");
}
return null;
logger.debug("Calling subflow '" + subflow.getId() + "'");
}
context.start(subflow, attributeMapper.createFlowInput(context));
}
/**
* Called on completion of the subflow to handle the subflow result event as determined by the end state reached by
* the subflow.
*/
public ViewSelection onEvent(RequestControlContext context) {
mapSubflowOutput(context.getLastEvent().getAttributes(), context);
return super.onEvent(context);
}
/**
* Map the output data produced by the subflow back into the request context (typically flow scope).
*/
private void mapSubflowOutput(AttributeMap subflowOutput, RequestContext context) {
if (getAttributeMapper() != null) {
if (logger.isDebugEnabled()) {
logger
.debug("Messaging the configured attribute mapper to map subflow result attributes to the "
+ "resuming parent flow -- It will have access to attributes passed up by the completed subflow");
}
attributeMapper.mapFlowOutput(subflowOutput, context);
} else {
if (logger.isDebugEnabled()) {
logger
.debug("No attribute mapper is configured for the resuming subflow state '"
+ getId()
+ "' -- As a result, no attributes of the ending flow will be passed to the resuming parent flow");
}
}
public void handleEvent(RequestControlContext context) {
attributeMapper.mapFlowOutput(context.getLastEvent().getAttributes(), context);
super.handleEvent(context);
}
protected void appendToString(ToStringCreator creator) {
creator.append("subflow", subflow.getId()).append("attributeMapper", attributeMapper);
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

@@ -23,13 +23,12 @@ import org.springframework.webflow.definition.TransitionDefinition;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.FlowExecutionException;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.ViewSelection;
/**
* A path from one {@link TransitionableState state} to another {@link State state}.
* <p>
* When executed a transition takes a flow execution from its current state, called the <i>source state</i>, to another
* state, called the </i>target state</i>. A transition may become eligible for execution on the occurence of an
* state, called the </i>target state</i>. A transition may become eligible for execution on the occurrence of an
* {@link Event} from within a transitionable source state.
* <p>
* When an event occurs within this transition's source <code>TransitionableState</code> the determination of the
@@ -163,7 +162,7 @@ public class Transition extends AnnotatedObject implements TransitionDefinition
}
/**
* Checks if this transition is elligible for execution given the state of the provided flow execution request
* Checks if this transition is eligible for execution given the state of the provided flow execution request
* context.
* @param context the flow execution request context
* @return true if this transition should execute, false otherwise
@@ -183,15 +182,13 @@ public class Transition extends AnnotatedObject implements TransitionDefinition
}
/**
* Execute this state transition. Will only be called if the {@link #matches(RequestContext)} method returns true
* for given context.
* Execute this state transition. Should only be called if the {@link #matches(RequestContext)} method returns true
* for the given context.
* @param sourceState the source state to transition from, may be null if the current state is null
* @param context the flow execution control context
* @return a view selection containing model and view information needed to render the results of the transition
* execution
* @throws FlowExecutionException when transition execution fails
*/
public ViewSelection execute(State sourceState, RequestControlContext context) throws FlowExecutionException {
ViewSelection selectedView;
public void execute(State sourceState, RequestControlContext context) throws FlowExecutionException {
if (canExecute(context)) {
if (sourceState != null) {
if (logger.isDebugEnabled()) {
@@ -209,11 +206,10 @@ public class Transition extends AnnotatedObject implements TransitionDefinition
State targetState = targetStateResolver.resolveTargetState(this, sourceState, context);
context.setLastTransition(this);
// enter the target state (note: any exceptions are propagated)
selectedView = targetState.enter(context);
targetState.enter(context);
} else {
if (sourceState != null && sourceState instanceof TransitionableState) {
// 'roll back' and re-enter the transitionable source state
selectedView = ((TransitionableState) sourceState).reenter(context);
((TransitionableState) sourceState).reenter(context);
} else {
throw new IllegalStateException("Execution of '" + this + "' was blocked by '" + getExecutionCriteria()
+ "', " + "; however, no source state is set at runtime. "
@@ -222,13 +218,12 @@ public class Transition extends AnnotatedObject implements TransitionDefinition
}
if (logger.isDebugEnabled()) {
if (context.getFlowExecutionContext().isActive()) {
logger.debug("Completed execution of " + this + ", as a result the new state is '"
logger.debug("Completed execution of " + this + "; as a result, the new state is '"
+ context.getCurrentState().getId() + "' in flow '" + context.getActiveFlow().getId() + "'");
} else {
logger.debug("Completed execution of " + this + ", as a result the flow execution has ended");
logger.debug("Completed execution of " + this + "; as a result, the flow execution has ended");
}
}
return selectedView;
}
public String toString() {

View File

@@ -20,7 +20,6 @@ import org.springframework.core.style.ToStringCreator;
import org.springframework.webflow.definition.TransitionDefinition;
import org.springframework.webflow.definition.TransitionableStateDefinition;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.ViewSelection;
/**
* Abstract superclass for states that can execute a transition in response to an event.
@@ -100,11 +99,10 @@ public abstract class TransitionableState extends State implements Transitionabl
* Inform this state definition that an event was signaled in it. The signaled event is the last event available in
* given request context ({@link RequestContext#getLastEvent()}).
* @param context the flow execution control context
* @return the selected view
* @throws NoMatchingTransitionException when a matching transition cannot be found
*/
public ViewSelection onEvent(RequestControlContext context) throws NoMatchingTransitionException {
return getRequiredTransition(context).execute(this, context);
public void handleEvent(RequestControlContext context) throws NoMatchingTransitionException {
context.execute(getRequiredTransition(context));
}
/**
@@ -113,11 +111,9 @@ public abstract class TransitionableState extends State implements Transitionabl
* <p>
* By default, this just calls <code>enter()</code>.
* @param context the flow control context in an executing flow (a client instance of a flow)
* @return a view selection containing model and view information needed to render the results of the state
* processing
*/
public ViewSelection reenter(RequestControlContext context) {
return enter(context);
public void reenter(RequestControlContext context) {
enter(context);
}
/**

View File

@@ -1,69 +0,0 @@
/*
* Copyright 2004-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.webflow.engine;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.ViewSelection;
/**
* Factory that produces a new, configured {@link ViewSelection} object on each invocation, taking into account the
* information in the provided flow execution request context.
* <p>
* Note: this class is a runtime factory. Instances are used at flow execution time by objects like the
* {@link ViewState} to produce new {@link ViewSelection view selections}.
* <p>
* This class allows for easy insertion of dynamic view selection logic, for instance, letting you determine the view to
* render or the available model data for rendering based on contextual information.
*
* @see org.springframework.webflow.execution.ViewSelection
* @see org.springframework.webflow.engine.ViewState
* @see org.springframework.webflow.engine.EndState
*
* @author Keith Donald
* @author Erwin Vervaet
*/
public interface ViewSelector {
/**
* Will the primary selection returned by 'makeEntrySelection' for the given request context be renderable in this
* request?
* <p>
* "Renderable" view selections typically can have 'render-actions' execute before they are created. An example
* would be an ApplicationView that forwards to a view template like a JSP. "Non-renderable" view selections are
* things like a flow execution redirect--no render actually occurs, but only a redirect--rendering happens on the
* new redirect request.
* @param context the current request context of the executing flow
* @return true if yes, false otherwise
*/
public boolean isEntrySelectionRenderable(RequestContext context);
/**
* Make a new "entry" view selection for the given request context. Called when a view-state, end-state, or other
* interactive state type is entered.
* @param context the current request context of the executing flow
* @return the entry view selection
*/
public ViewSelection makeEntrySelection(RequestContext context);
/**
* Reconstitute a renderable view selection for the given request context to support a ViewState 'refresh'
* operation.
* @param context the current request context of the executing flow
* @return the view selection
*/
public ViewSelection makeRefreshSelection(RequestContext context);
}

View File

@@ -18,16 +18,14 @@ package org.springframework.webflow.engine;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.webflow.execution.FlowExecutionException;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.ViewSelection;
import org.springframework.webflow.execution.View;
import org.springframework.webflow.execution.ViewFactory;
/**
* A view state is a state that issues a response to the user, for example, for soliciting form input.
* <p>
* To accomplish this, a <code>ViewState</code> makes a {@link ViewSelection}, which contains the necessary
* information to issue a suitable response.
* A view state is a state that issues a response to the user, for example, for soliciting form input. To accomplish
* this, a <code>ViewState</code> delegates to a {@link ViewFactory}.
*
* @see org.springframework.webflow.engine.ViewSelector
* @see ViewFactory
*
* @author Keith Donald
* @author Erwin Vervaet
@@ -40,33 +38,34 @@ public class ViewState extends TransitionableState {
private ActionList renderActionList = new ActionList();
/**
* The factory for the view selection to return when this state is entered.
* Whether or not a redirect should occur before the view is rendered.
*/
private ViewSelector viewSelector = NullViewSelector.INSTANCE;
private boolean redirect;
/**
* A factory for creating and restoring the view rendered by this view state.
*/
private ViewFactory viewFactory;
/**
* Create a new view state.
* @param flow the owning flow
* @param id the state identifier (must be unique to the flow)
* @param viewFactory the view factory
* @throws IllegalArgumentException when this state cannot be added to given flow, e.g. because the id is not unique
*/
public ViewState(Flow flow, String id) throws IllegalArgumentException {
public ViewState(Flow flow, String id, ViewFactory viewFactory) throws IllegalArgumentException {
super(flow, id);
Assert.notNull(viewFactory, "The view factory is required");
this.viewFactory = viewFactory;
}
/**
* Returns the strategy used to select the view to render in this view state.
* Sets whether this view state should send a flow execution redirect when entered.
* @param redirect the redirect flag
*/
public ViewSelector getViewSelector() {
return viewSelector;
}
/**
* Sets the strategy used to select the view to render in this view state.
*/
public void setViewSelector(ViewSelector viewSelector) {
Assert.notNull(viewSelector, "The view selector to make view selections is required");
this.viewSelector = viewSelector;
public void setRedirect(boolean redirect) {
this.redirect = redirect;
}
/**
@@ -77,40 +76,37 @@ public class ViewState extends TransitionableState {
return renderActionList;
}
/**
* Specialization of State's <code>doEnter</code> template method that executes behavior specific to this state
* type in polymorphic fashion.
* <p>
* Returns a view selection indicating a response to issue. The view selection typically contains all the data
* necessary to issue the response.
* @param context the control context for the currently executing flow, used by this state to manipulate the flow
* execution
* @return a view selection serving as a response instruction
* @throws FlowExecutionException if an exception occurs in this state
*/
protected ViewSelection doEnter(RequestControlContext context) throws FlowExecutionException {
if (viewSelector.isEntrySelectionRenderable(context)) {
// the entry selection will be rendered!
protected void doEnter(RequestControlContext context) throws FlowExecutionException {
context.assignFlowExecutionKey();
if (shouldRedirect(context)) {
context.sendFlowExecutionRedirect();
} else {
View view = viewFactory.getView(context);
renderActionList.execute(context);
view.render();
context.getMessageContext().clearMessages();
context.getFlashScope().clear();
}
return viewSelector.makeEntrySelection(context);
}
/**
* Request that the current view selection be reconstituted to support reissuing the response. This is an idempotent
* operation that may be safely called any number of times on a paused execution, used primarily to support a flow
* execution redirect.
* @param context the request context
* @return the view selection
* @throws FlowExecutionException if an exception occurs in this state
*/
public ViewSelection refresh(RequestContext context) throws FlowExecutionException {
renderActionList.execute(context);
return viewSelector.makeRefreshSelection(context);
public void resume(RequestControlContext context) {
View view = viewFactory.getView(context);
if (view.eventSignaled()) {
context.handleEvent(view.getEvent());
} else {
renderActionList.execute(context);
view.render();
context.getMessageContext().clearMessages();
context.getFlashScope().clear();
}
}
private boolean shouldRedirect(RequestControlContext context) {
return redirect || context.getAlwaysRedirectOnPause();
}
protected void appendToString(ToStringCreator creator) {
creator.append("viewSelector", viewSelector);
super.appendToString(creator);
creator.append("viewManager", viewFactory);
}
}

View File

@@ -1,881 +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.builder;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.SettableExpression;
import org.springframework.binding.mapping.AttributeMapper;
import org.springframework.binding.mapping.Mapping;
import org.springframework.binding.mapping.MappingBuilder;
import org.springframework.binding.method.MethodSignature;
import org.springframework.core.style.ToStringCreator;
import org.springframework.webflow.action.AbstractBeanInvokingAction;
import org.springframework.webflow.action.ActionResultExposer;
import org.springframework.webflow.action.BeanInvokingActionFactory;
import org.springframework.webflow.action.EvaluateAction;
import org.springframework.webflow.action.MultiAction;
import org.springframework.webflow.core.collection.AttributeMap;
import org.springframework.webflow.core.collection.CollectionUtils;
import org.springframework.webflow.engine.AnnotatedAction;
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.TargetStateResolver;
import org.springframework.webflow.engine.Transition;
import org.springframework.webflow.engine.TransitionCriteria;
import org.springframework.webflow.engine.ViewSelector;
import org.springframework.webflow.engine.support.ActionTransitionCriteria;
import org.springframework.webflow.execution.Action;
import org.springframework.webflow.execution.ScopeType;
import org.springframework.webflow.execution.support.EventFactorySupport;
/**
* Base class for flow builders that programmatically build flows in Java configuration code.
* <p>
* To give you an example of what a simple Java-based web flow builder definition might look like, the following example
* defines the 'dynamic' web flow roughly equivalent to the work flow statically implemented in Spring MVC's simple form
* controller:
*
* <pre class="code">
* public class CustomerDetailFlowBuilder extends AbstractFlowBuilder {
* public void buildStates() {
* // get customer information
* addActionState(&quot;getDetails&quot;, action(&quot;customerAction&quot;), transition(on(success()), to(&quot;displayDetails&quot;)));
*
* // view customer information
* addViewState(&quot;displayDetails&quot;, &quot;customerDetails&quot;, transition(on(submit()), to(&quot;bindAndValidate&quot;)));
*
* // bind and validate customer information updates
* addActionState(&quot;bindAndValidate&quot;, action(&quot;customerAction&quot;), new Transition[] {
* transition(on(error()), to(&quot;displayDetails&quot;)), transition(on(success()), to(&quot;finish&quot;)) });
*
* // finish
* addEndState(&quot;finish&quot;);
* }
* }
* </pre>
*
* What this Java-based FlowBuilder implementation does is add four states to a flow. These include a "get"
* <code>ActionState</code> (the start state), a <code>ViewState</code> state, a "bind and validate"
* <code>ActionState</code>, and an end marker state (<code>EndState</code>).
* <p>
* The first state, an action state, will be assigned the indentifier <code>getDetails</code>. This action state will
* automatically be configured with the following defaults:
* <ol>
* <li>The action instance with id <code>customerAction</code>. This is the <code>Action</code> implementation
* that will execute when this state is entered. In this example, that <code>Action</code> will go out to the DB, load
* the Customer, and put it in the Flow's request context.
* <li>A <code>success</code> transition to a default view state, called <code>displayDetails</code>. This means
* when the <code>Action</code> returns a <code>success</code> result event (aka outcome), the
* <code>displayDetails</code> state will be entered.
* <li>It will act as the start state for this flow (by default, the first state added to a flow during the build
* process is treated as the start state).
* </ol>
* <p>
* The second state, a view state, will be identified as <code>displayDetails</code>. This view state will
* automatically be configured with the following defaults:
* <ol>
* <li>A view name called <code>customerDetails</code>. This is the logical name of a view resource. This logical
* view name gets mapped to a physical view resource (jsp, etc.) by the calling front controller (via a Spring view
* resolver, or a Struts action forward, for example).
* <li>A <code>submit</code> transition to a bind and validate action state, indentified by the default id
* <code>bindAndValidate</code>. This means when a <code>submit</code> event is signaled by the view (for example,
* on a submit button click), the bindAndValidate action state will be entered and the <code>bindAndValidate</code>
* method of the <code>customerAction</code> <code>Action</code> implementation will be executed.
* </ol>
* <p>
* The third state, an action state, will be indentified as <code>bindAndValidate</code>. This action state will
* automatically be configured with the following defaults:
* <ol>
* <li>An action bean named <code>customerAction</code> -- this is the name of the <code>Action</code>
* implementation exported in the application context that will execute when this state is entered. In this example, the
* <code>Action</code> has a "bindAndValidate" method that will bind form input in the HTTP request to a backing
* Customer form object, validate it, and update the DB.
* <li>A <code>success</code> transition to a default end state, called <code>finish</code>. This means if the
* <code>Action</code> returns a <code>success</code> result, the <code>finish</code> end state will be
* transitioned to and the flow will terminate.
* <li>An <code>error</code> transition back to the form view. This means if the <code>Action</code> returns an
* <code>error</code> event, the <code>
* displayDetails</code> view state will be transitioned back to.
* </ol>
* <p>
* The fourth and last state, an end state, will be indentified with the default end state id <code>finish</code>.
* This end state is a marker that signals the end of the flow. When entered, the flow session terminates, and if this
* flow is acting as a root flow in the current flow execution, any flow-allocated resources will be cleaned up. An end
* state can optionally be configured with a logical view name to forward to when entered. It will also trigger a state
* transition in a resuming parent flow if this flow was participating as a spawned 'subflow' within a suspended parent
* flow.
*
* @author Keith Donald
* @author Erwin Vervaet
*/
public abstract class AbstractFlowBuilder extends BaseFlowBuilder {
/**
* A helper for creating commonly used event identifiers that drive transitions created by this builder.
*/
private EventFactorySupport eventFactorySupport = new EventFactorySupport();
/**
* Default constructor for subclassing.
*/
protected AbstractFlowBuilder() {
super();
}
/**
* Create an instance of an abstract flow builder, using the specified locator to obtain needed flow services at
* build time.
* @param flowServiceLocator the locator for services needed by this builder to build its Flow
*/
protected AbstractFlowBuilder(FlowServiceLocator flowServiceLocator) {
super(flowServiceLocator);
}
/**
* Returns the configured event factory support helper for creating commonly used event identifiers that drive
* transitions created by this builder.
*/
public EventFactorySupport getEventFactorySupport() {
return eventFactorySupport;
}
/**
* Sets the event factory support helper to use to create commonly used event identifiers that drive transitions
* created by this builder.
*/
public void setEventFactorySupport(EventFactorySupport eventFactorySupport) {
this.eventFactorySupport = eventFactorySupport;
}
public void init(String flowId, AttributeMap attributes) throws FlowBuilderException {
setFlow(getFlowArtifactFactory().createFlow(flowId, flowAttributes().union(attributes)));
initBuilder();
}
/**
* Hook subclasses may override to provide additional properties for the flow built by this builder. Returns a empty
* collection by default.
* @return additional properties describing the flow being built, should not return null
*/
protected AttributeMap flowAttributes() {
return CollectionUtils.EMPTY_ATTRIBUTE_MAP;
}
/**
* Hook method subclasses can override to initialize the flow builder. Will be called by
* {@link #init(String, AttributeMap)} after creating the initial Flow object. As a consequence, {@link #getFlow()}
* can be called to retrieve the Flow object under construction.
*/
protected void initBuilder() {
}
// view state
/**
* Adds a view state to the flow built by this builder.
* @param stateId the state identifier
* @param viewName the string-encoded view selector
* @param transition the sole transition (path) out of this state
* @return the fully constructed view state instance
*/
protected State addViewState(String stateId, String viewName, Transition transition) {
return getFlowArtifactFactory().createViewState(stateId, getFlow(), null, viewSelector(viewName), null,
new Transition[] { transition }, null, null, null);
}
/**
* Adds a view state to the flow built by this builder.
* @param stateId the state identifier
* @param viewName the string-encoded view selector
* @param transitions the transitions (paths) out of this state
* @return the fully constructed view state instance
*/
protected State addViewState(String stateId, String viewName, Transition[] transitions) {
return getFlowArtifactFactory().createViewState(stateId, getFlow(), null, viewSelector(viewName), null,
transitions, null, null, null);
}
/**
* Adds a view state to the flow built by this builder.
* @param stateId the state identifier
* @param viewName the string-encoded view selector
* @param renderAction the action to execute on state entry and refresh; may be null
* @param transition the sole transition (path) out of this state
* @return the fully constructed view state instance
*/
protected State addViewState(String stateId, String viewName, Action renderAction, Transition transition) {
return getFlowArtifactFactory().createViewState(stateId, getFlow(), null, viewSelector(viewName),
new Action[] { renderAction }, new Transition[] { transition }, null, null, null);
}
/**
* Adds a view state to the flow built by this builder.
* @param stateId the state identifier
* @param viewName the string-encoded view selector
* @param renderAction the action to execute on state entry and refresh; may be null
* @param transitions the transitions (paths) out of this state
* @return the fully constructed view state instance
*/
protected State addViewState(String stateId, String viewName, Action renderAction, Transition[] transitions) {
return getFlowArtifactFactory().createViewState(stateId, getFlow(), null, viewSelector(viewName),
new Action[] { renderAction }, transitions, null, null, null);
}
/**
* Adds a view state to the flow built by this builder.
* @param stateId the state identifier
* @param entryActions the actions to execute when the state is entered
* @param viewSelector the view selector that will make the view selection when the state is entered
* @param renderActions any 'render actions' to execute on state entry and refresh; may be null
* @param transitions the transitions (path) out of this state
* @param exceptionHandlers any exception handlers to attach to the state
* @param exitActions the actions to execute when the state exits
* @param attributes attributes to assign to the state that may be used to affect state construction and execution
* @return the fully constructed view state instance
*/
protected State addViewState(String stateId, Action[] entryActions, ViewSelector viewSelector,
Action[] renderActions, Transition[] transitions, FlowExecutionExceptionHandler[] exceptionHandlers,
Action[] exitActions, AttributeMap attributes) {
return getFlowArtifactFactory().createViewState(stateId, getFlow(), entryActions, viewSelector, renderActions,
transitions, exceptionHandlers, exitActions, attributes);
}
// action state
/**
* Adds an action state to the flow built by this builder.
* @param stateId the state identifier
* @param action the single action to execute when the state is entered
* @param transition the single transition (path) out of this state
* @return the fully constructed action state instance
*/
protected State addActionState(String stateId, Action action, Transition transition) {
return getFlowArtifactFactory().createActionState(stateId, getFlow(), null, new Action[] { action },
new Transition[] { transition }, null, null, null);
}
/**
* Adds an action state to the flow built by this builder.
* @param stateId the state identifier
* @param action the single action to execute when the state is entered
* @param transitions the transitions (paths) out of this state
* @return the fully constructed action state instance
*/
protected State addActionState(String stateId, Action action, Transition[] transitions) {
return getFlowArtifactFactory().createActionState(stateId, getFlow(), null, new Action[] { action },
transitions, null, null, null);
}
/**
* Adds an action state to the flow built by this builder.
* @param stateId the state identifier
* @param action the single action to execute when the state is entered
* @param transition the single transition (path) out of this state
* @param exceptionHandler the exception handler to handle exceptions thrown by the action
* @return the fully constructed action state instance
*/
protected State addActionState(String stateId, Action action, Transition transition,
FlowExecutionExceptionHandler exceptionHandler) {
return getFlowArtifactFactory().createActionState(stateId, getFlow(), null, new Action[] { action },
new Transition[] { transition }, new FlowExecutionExceptionHandler[] { exceptionHandler }, null, null);
}
/**
* Adds an action state to the flow built by this builder.
* @param stateId the state identifier
* @param entryActions any generic entry actions to add to the state
* @param actions the actions to execute in a chain when the state is entered
* @param transitions the transitions (paths) out of this state
* @param exceptionHandlers the exception handlers to handle exceptions thrown by the actions
* @param exitActions the exit actions to execute when the state exits
* @param attributes attributes to assign to the state that may be used to affect state construction and execution
* @return the fully constructed action state instance
*/
protected State addActionState(String stateId, Action[] entryActions, Action[] actions, Transition[] transitions,
FlowExecutionExceptionHandler[] exceptionHandlers, Action[] exitActions, AttributeMap attributes) {
return getFlowArtifactFactory().createActionState(stateId, getFlow(), entryActions, actions, transitions,
exceptionHandlers, exitActions, attributes);
}
// decision state
/**
* Adds a decision state to the flow built by this builder.
* @param stateId the state identifier
* @param transitions the transitions (paths) out of this state
* @return the fully constructed decision state instance
*/
protected State addDecisionState(String stateId, Transition[] transitions) {
return getFlowArtifactFactory().createDecisionState(stateId, getFlow(), null, transitions, null, null, null);
}
/**
* Adds a decision state to the flow built by this builder.
* @param stateId the state identifier
* @param decisionCriteria the criteria that defines the decision
* @param trueStateId the target state on a "true" decision
* @param falseStateId the target state on a "false" decision
* @return the fully constructed decision state instance
*/
protected State addDecisionState(String stateId, TransitionCriteria decisionCriteria, String trueStateId,
String falseStateId) {
Transition thenTransition = getFlowArtifactFactory().createTransition(to(trueStateId), decisionCriteria, null,
null);
Transition elseTransition = getFlowArtifactFactory().createTransition(to(falseStateId), null, null, null);
return getFlowArtifactFactory().createDecisionState(stateId, getFlow(), null,
new Transition[] { thenTransition, elseTransition }, null, null, null);
}
/**
* Adds a decision state to the flow built by this builder.
* @param stateId the state identifier
* @param entryActions the entry actions to execute when the state enters
* @param transitions the transitions (paths) out of this state
* @param exceptionHandlers the exception handlers to handle exceptions thrown by the state
* @param exitActions the exit actions to execute when the state exits
* @param attributes attributes to assign to the state that may be used to affect state construction and execution
* @return the fully constructed decision state instance
*/
protected State addDecisionState(String stateId, Action[] entryActions, Transition[] transitions,
FlowExecutionExceptionHandler[] exceptionHandlers, Action[] exitActions, AttributeMap attributes) {
return getFlowArtifactFactory().createDecisionState(stateId, getFlow(), entryActions, transitions,
exceptionHandlers, exitActions, attributes);
}
// subflow state
/**
* Adds a subflow state to the flow built by this builder.
* @param stateId the state identifier
* @param subflow the flow that will act as the subflow
* @param attributeMapper the mapper to map subflow input and output attributes
* @param transition the single transition (path) out of the state
* @return the fully constructed subflow state instance
*/
protected State addSubflowState(String stateId, Flow subflow, FlowAttributeMapper attributeMapper,
Transition transition) {
return getFlowArtifactFactory().createSubflowState(stateId, getFlow(), null, subflow, attributeMapper,
new Transition[] { transition }, null, null, null);
}
/**
* Adds a subflow state to the flow built by this builder.
* @param stateId the state identifier
* @param subflow the flow that will act as the subflow
* @param attributeMapper the mapper to map subflow input and output attributes
* @param transitions the transitions (paths) out of the state
* @return the fully constructed subflow state instance
*/
protected State addSubflowState(String stateId, Flow subflow, FlowAttributeMapper attributeMapper,
Transition[] transitions) {
return getFlowArtifactFactory().createSubflowState(stateId, getFlow(), null, subflow, attributeMapper,
transitions, null, null, null);
}
/**
* Adds a subflow state to the flow built by this builder.
* @param stateId the state identifier
* @param entryActions the entry actions to execute when the state enters
* @param subflow the flow that will act as the subflow
* @param attributeMapper the mapper to map subflow input and output attributes
* @param transitions the transitions (paths) out of this state
* @param exceptionHandlers the exception handlers to handle exceptions thrown by the state
* @param exitActions the exit actions to execute when the state exits
* @param attributes attributes to assign to the state that may be used to affect state construction and execution
* @return the fully constructed subflow state instance
*/
protected State addSubflowState(String stateId, Action[] entryActions, Flow subflow,
FlowAttributeMapper attributeMapper, Transition[] transitions,
FlowExecutionExceptionHandler[] exceptionHandlers, Action[] exitActions, AttributeMap attributes) {
return getFlowArtifactFactory().createSubflowState(stateId, getFlow(), entryActions, subflow, attributeMapper,
transitions, exceptionHandlers, exitActions, attributes);
}
// end state
/**
* Adds an end state to the flow built by this builder.
* @param stateId the state identifier
* @return the fully constructed end state instance
*/
protected State addEndState(String stateId) {
return getFlowArtifactFactory().createEndState(stateId, getFlow(), null, null, null, null, null);
}
/**
* Adds an end state to the flow built by this builder.
* @param stateId the state identifier
* @param viewName the string-encoded view selector
* @return the fully constructed end state instance
*/
protected State addEndState(String stateId, String viewName) {
return getFlowArtifactFactory().createEndState(stateId, getFlow(), null, viewSelector(viewName), null, null,
null);
}
/**
* Adds an end state to the flow built by this builder.
* @param stateId the state identifier
* @param viewName the string-encoded view selector
* @param outputMapper the output mapper to map output attributes for the end state (a flow outcome)
* @return the fully constructed end state instance
*/
protected State addEndState(String stateId, String viewName, AttributeMapper outputMapper) {
return getFlowArtifactFactory().createEndState(stateId, getFlow(), null, viewSelector(viewName), outputMapper,
null, null);
}
/**
* Adds an end state to the flow built by this builder.
* @param stateId the state identifier
* @param entryActions the actions to execute when the state is entered
* @param viewSelector the view selector that will make the view selection when the state is entered
* @param outputMapper the output mapper to map output attributes for the end state (a flow outcome)
* @param exceptionHandlers any exception handlers to attach to the state
* @param attributes attributes to assign to the state that may be used to affect state construction and execution
* @return the fully constructed end state instance
*/
protected State addEndState(String stateId, Action[] entryActions, ViewSelector viewSelector,
AttributeMapper outputMapper, FlowExecutionExceptionHandler[] exceptionHandlers, AttributeMap attributes) {
return getFlowArtifactFactory().createEndState(stateId, getFlow(), entryActions, viewSelector, outputMapper,
exceptionHandlers, attributes);
}
// helpers to create misc. flow artifacts
/**
* Factory method that creates a view selector from an encoded view name. See {@link TextToViewSelector} for
* information on the conversion rules.
* @param viewName the encoded view selector
* @return the view selector
*/
public ViewSelector viewSelector(String viewName) {
return (ViewSelector) fromStringTo(ViewSelector.class).execute(viewName);
}
/**
* Resolves the action with the specified id. Simply looks the action up by id and returns it.
* @param id the action id
* @return the action
* @throws FlowArtifactLookupException the action could not be resolved
*/
protected Action action(String id) throws FlowArtifactLookupException {
return getFlowServiceLocator().getAction(id);
}
/**
* Creates a bean invoking action that invokes the method identified by the signature on the bean associated with
* the action identifier.
* @param beanId the id identifying an arbitrary <code>java.lang.Object</code> to be used as an action
* @param methodSignature the signature of the method to invoke on the POJO
* @return the adapted bean invoking action
* @throws FlowArtifactLookupException the action could not be resolved
*/
protected Action action(String beanId, MethodSignature methodSignature) throws FlowArtifactLookupException {
return getBeanInvokingActionFactory().createBeanInvokingAction(beanId,
getFlowServiceLocator().getBeanFactory(), methodSignature, null,
getFlowServiceLocator().getConversionService(), null);
}
/**
* Creates a bean invoking action that invokes the method identified by the signature on the bean associated with
* the action identifier.
* @param beanId the id identifying an arbitrary <code>java.lang.Object</code> to be used as an action
* @param methodSignature the signature of the method to invoke on the POJO
* @return the adapted bean invoking action
* @throws FlowArtifactLookupException the action could not be resolved
*/
protected Action action(String beanId, MethodSignature methodSignature, ActionResultExposer resultExposer)
throws FlowArtifactLookupException {
return getBeanInvokingActionFactory().createBeanInvokingAction(beanId,
getFlowServiceLocator().getBeanFactory(), methodSignature, resultExposer,
getFlowServiceLocator().getConversionService(), null);
}
/**
* Creates an evaluate action that evaluates the expression when executed.
* @param expression the expression to evaluate
*/
protected Action action(Expression expression) {
return action(expression, null);
}
/**
* Creates an evaluate action that evaluates the expression when executed.
* @param expression the expression to evaluate
* @param resultExposer the evaluation result exposer
*/
protected Action action(Expression expression, ActionResultExposer resultExposer) {
return new EvaluateAction(expression, resultExposer);
}
/**
* Parses the expression string into an evaluatable {@link Expression} object.
* @param expressionString the expression string, e.g. flowScope.order.number
* @return the evaluatable expression
*/
protected Expression expression(String expressionString) {
return getFlowServiceLocator().getExpressionParser().parseExpression(expressionString);
}
/**
* Parses the expression string into a settable {@link Expression} object.
* @param expressionString the expression string, e.g. flowScope.order.number
* @return the evaluatable expression
* @since 1.0.2
*/
protected SettableExpression settableExpression(String expressionString) {
return getFlowServiceLocator().getExpressionParser().parseSettableExpression(expressionString);
}
/**
* Convert the encoded method signature string to a {@link MethodSignature} object. Method signatures are used to
* match methods on POJO services to invoke on a {@link AbstractBeanInvokingAction bean invoking action}.
* <p>
* Encoded method signature format:
*
* Method without arguments:
*
* <pre>
* ${methodName}
* </pre>
*
* Method with arguments:
*
* <pre>
* ${methodName}(${arg1}, ${arg2}, ${arg n})
* </pre>
*
* @param method the encoded method signature
* @return the method signature
* @see #action(String, MethodSignature, ActionResultExposer)
*/
protected MethodSignature method(String method) {
return (MethodSignature) fromStringTo(MethodSignature.class).execute(method);
}
/**
* Factory method for a {@link ActionResultExposer result exposer}. A result exposer is used to expose an action
* result such as a method return value or expression evaluation result to the calling flow.
* @param resultName the result name
* @return the result exposer
* @see #action(String, MethodSignature, ActionResultExposer)
*/
protected ActionResultExposer result(String resultName) {
return result(resultName, ScopeType.REQUEST);
}
/**
* Factory method for a {@link ActionResultExposer result exposer}. A result exposer is used to expose an action
* result such as a method return value or expression evaluation result to the calling flow.
* @param resultName the result name
* @param resultScope the scope of the result
* @return the result exposer
* @see #action(String, MethodSignature, ActionResultExposer)
*/
protected ActionResultExposer result(String resultName, ScopeType resultScope) {
return new ActionResultExposer(resultName, resultScope);
}
/**
* Wrap given action in an {@link AnnotatedAction}} to be able to annotate it with attributes.
* @param action the action to annotate
* @return the wrapped action
* @since 1.0.4
*/
protected AnnotatedAction annotate(Action action) {
if (action instanceof AnnotatedAction) {
return (AnnotatedAction) action;
} else {
return new AnnotatedAction(action);
}
}
/**
* Creates an annotated action decorator that instructs the specified method be invoked on the multi action when it
* is executed. Use this when working with MultiActions to specify the method on the MultiAction to invoke for a
* particular usage scenario. Use the {@link #method(String)} factory method when working with
* {@link AbstractBeanInvokingAction bean invoking actions}.
* @param methodName the name of the method on the multi action instance
* @param multiAction the multi action
* @return the annotated action that when invoked sets up a context property used by the multi action to instruct it
* with what method to invoke
* @since 1.0.4
*/
protected AnnotatedAction invoke(String methodName, Action multiAction) {
AnnotatedAction annotatedAction;
if (multiAction instanceof AnnotatedAction) {
// already wrapped in an AnnotatedAction
annotatedAction = (AnnotatedAction) multiAction;
} else {
annotatedAction = new AnnotatedAction(multiAction);
}
annotatedAction.setMethod(methodName);
return annotatedAction;
}
/**
* Creates an annotated action decorator that instructs the specified method be invoked on the multi action when it
* is executed. Use this when working with MultiActions to specify the method on the MultiAction to invoke for a
* particular usage scenario. Use the {@link #method(String)} factory method when working with
* {@link AbstractBeanInvokingAction bean invoking actions}.
* @param methodName the name of the method on the multi action instance
* @param multiAction the multi action
* @return the annotated action that when invoked sets up a context property used by the multi action to instruct it
* with what method to invoke
*/
protected AnnotatedAction invoke(String methodName, MultiAction multiAction) throws FlowArtifactLookupException {
return invoke(methodName, (Action) multiAction);
}
/**
* Creates an annotated action decorator that makes the given action an named action.
* @param name the action name
* @param action the action to name
* @return the annotated named action
*/
protected AnnotatedAction name(String name, Action action) {
AnnotatedAction annotatedAction;
if (action instanceof AnnotatedAction) {
// already wrapped in an AnnotatedAction
annotatedAction = (AnnotatedAction) action;
} else {
annotatedAction = new AnnotatedAction(action);
}
annotatedAction.setName(name);
return annotatedAction;
}
/**
* Request that the attribute mapper with the specified name be used to map attributes between a parent flow and a
* spawning subflow when the subflow state being constructed is entered.
* @param id the id of the attribute mapper that will map attributes between the flow built by this builder and the
* subflow
* @return the attribute mapper
* @throws FlowArtifactLookupException no FlowAttributeMapper implementation was exported with the specified id
*/
protected FlowAttributeMapper attributeMapper(String id) throws FlowArtifactLookupException {
return getFlowServiceLocator().getAttributeMapper(id);
}
/**
* Request that the <code>Flow</code> with the specified flowId be spawned as a subflow when the subflow state
* being built is entered. Simply resolves the subflow definition by id and returns it; throwing a fail-fast
* exception if it does not exist.
* @param id the flow definition id
* @return the flow to be used as a subflow, this should be passed to a addSubflowState call
* @throws FlowArtifactLookupException when the flow cannot be resolved
*/
protected Flow flow(String id) throws FlowArtifactLookupException {
return getFlowServiceLocator().getSubflow(id);
}
/**
* Creates a transition criteria that is used to match a Transition. The criteria is based on the provided
* expression string.
* @param transitionCriteriaExpression the transition criteria expression, typically simply a static event
* identifier (e.g. "submit")
* @return the transition criteria
* @see TextToTransitionCriteria
*/
protected TransitionCriteria on(String transitionCriteriaExpression) {
return (TransitionCriteria) fromStringTo(TransitionCriteria.class).execute(transitionCriteriaExpression);
}
/**
* Creates a target state resolver for the given state id expression.
* @param targetStateIdExpression the target state id expression
* @return the target state resolver
* @see TextToTargetStateResolver
*/
protected TargetStateResolver to(String targetStateIdExpression) {
return (TargetStateResolver) fromStringTo(TargetStateResolver.class).execute(targetStateIdExpression);
}
/**
* Creates a new transition.
* @param matchingCriteria the criteria that determines when the transition matches
* @param targetStateResolver the resolver of the transition's target state
* @return the transition
*/
protected Transition transition(TransitionCriteria matchingCriteria, TargetStateResolver targetStateResolver) {
return getFlowArtifactFactory().createTransition(targetStateResolver, matchingCriteria, null, null);
}
/**
* Creates a new transition.
* @param matchingCriteria the criteria that determines when the transition matches
* @param targetStateResolver the resolver of the transition's target state
* @param executionCriteria the criteria that determines if a matched transition is allowed to execute
* @return the transition
*/
protected Transition transition(TransitionCriteria matchingCriteria, TargetStateResolver targetStateResolver,
TransitionCriteria executionCriteria) {
return getFlowArtifactFactory()
.createTransition(targetStateResolver, matchingCriteria, executionCriteria, null);
}
/**
* Creates a new transition.
* @param matchingCriteria the criteria that determines when the transition matches
* @param targetStateResolver the resolver of the transition's target state
* @param executionCriteria the criteria that determines if a matched transition is allowed to execute
* @param attributes transition attributes
* @return the transition
*/
protected Transition transition(TransitionCriteria matchingCriteria, TargetStateResolver targetStateResolver,
TransitionCriteria executionCriteria, AttributeMap attributes) {
return getFlowArtifactFactory().createTransition(targetStateResolver, matchingCriteria, executionCriteria,
attributes);
}
/**
* Creates a <code>TransitionCriteria</code> that will execute the specified action when the Transition is
* executed but before the transition's target state is entered.
* <p>
* This criteria will only allow the Transition to complete execution if the Action completes successfully.
* @param action the action to execute after a transition is matched but before it transitions to its target state
* @return the transition execution criteria
*/
protected TransitionCriteria ifReturnedSuccess(Action action) {
return new ActionTransitionCriteria(action);
}
/**
* Creates the <code>success</code> event id. "Success" indicates that an action completed successfuly.
* @return the event id
*/
protected String success() {
return eventFactorySupport.getSuccessEventId();
}
/**
* Creates the <code>error</code> event id. "Error" indicates that an action completed with an error status.
* @return the event id
*/
protected String error() {
return eventFactorySupport.getErrorEventId();
}
/**
* Creates the <code>submit</code> event id. "Submit" indicates the user submitted a request (form) for
* processing.
* @return the event id
*/
protected String submit() {
return "submit";
}
/**
* Creates the <code>back</code> event id. "Back" indicates the user wants to go to the previous step in the flow.
* @return the event id
*/
protected String back() {
return "back";
}
/**
* Creates the <code>cancel</code> event id. "Cancel" indicates the flow was aborted because the user changed
* their mind.
* @return the event id
*/
protected String cancel() {
return "cancel";
}
/**
* Creates the <code>finish</code> event id. "Finish" indicates the flow has finished processing.
* @return the event id
*/
protected String finish() {
return "finish";
}
/**
* Creates the <code>select</code> event id. "Select" indicates an object was selected for processing or display.
* @return the event id
*/
protected String select() {
return "select";
}
/**
* Creates the <code>edit</code> event id. "Edit" indicates an object was selected for creation or updating.
* @return the event id
*/
protected String edit() {
return "edit";
}
/**
* Creates the <code>add</code> event id. "Add" indicates a child object is being added to a parent collection.
* @return the event id
*/
protected String add() {
return "add";
}
/**
* Creates the <code>delete</code> event id. "Delete" indicates a object is being removed.
* @return the event id
*/
protected String delete() {
return "delete";
}
/**
* Creates the <code>yes</code> event id. "Yes" indicates a true result was returned.
* @return the event id
*/
protected String yes() {
return eventFactorySupport.getYesEventId();
}
/**
* Creates the <code>no</code> event id. "False" indicates a false result was returned.
* @return the event id
*/
protected String no() {
return eventFactorySupport.getNoEventId();
}
/**
* Factory method that returns a new, fully configured mapping builder to assist with building {@link Mapping}
* objects used by a {@link FlowAttributeMapper} to map attributes.
* @return the mapping builder
*/
protected MappingBuilder mapping() {
MappingBuilder mapping = new MappingBuilder(getFlowServiceLocator().getExpressionParser());
mapping.setConversionService(getFlowServiceLocator().getConversionService());
return mapping;
}
public String toString() {
return new ToStringCreator(this).toString();
}
// internal helpers
private FlowArtifactFactory getFlowArtifactFactory() {
return getFlowServiceLocator().getFlowArtifactFactory();
}
private BeanInvokingActionFactory getBeanInvokingActionFactory() {
return getFlowServiceLocator().getBeanInvokingActionFactory();
}
}

View File

@@ -1,148 +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.builder;
import org.springframework.binding.convert.ConversionException;
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.util.Assert;
import org.springframework.webflow.core.collection.AttributeMap;
import org.springframework.webflow.engine.Flow;
/**
* Abstract base implementation of a flow builder defining common functionality needed by most concrete flow builder
* implementations. This class implements all optional parts of the FlowBuilder process as no-op methods. Subclasses are
* only required to implement {@link #init(String, AttributeMap)} and {@link #buildStates()}.
* <p>
* This class also provides a {@link FlowServiceLocator} for use by subclasses in the flow construction process.
*
* @see org.springframework.webflow.engine.builder.FlowServiceLocator
*
* @author Keith Donald
* @author Erwin Vervaet
*/
public abstract class BaseFlowBuilder implements FlowBuilder {
/**
* The <code>Flow</code> built by this builder.
*/
private Flow flow;
/**
* Locates actions, attribute mappers, and other artifacts needed by the flow built by this builder.
*/
private FlowServiceLocator flowServiceLocator;
/**
* Default constructor for subclassing. Sets up use of a {@link BaseFlowServiceLocator}.
* @see #setFlowServiceLocator(FlowServiceLocator)
*/
protected BaseFlowBuilder() {
setFlowServiceLocator(new BaseFlowServiceLocator());
}
/**
* Creates a flow builder using the given locator to link in artifacts.
* @param flowServiceLocator the locator for services needed by this builder to build its Flow
*/
protected BaseFlowBuilder(FlowServiceLocator flowServiceLocator) {
setFlowServiceLocator(flowServiceLocator);
}
/**
* Returns the configured flow service locator.
*/
public FlowServiceLocator getFlowServiceLocator() {
return flowServiceLocator;
}
/**
* Sets the flow service locator to use. Defaults to {@link BaseFlowServiceLocator}.
*/
public void setFlowServiceLocator(FlowServiceLocator flowServiceLocator) {
Assert.notNull(flowServiceLocator, "The flow service locator is required");
this.flowServiceLocator = flowServiceLocator;
}
/**
* Set the flow being built by this builder. Typically called during initialization to set the initial flow
* reference returned by {@link #getFlow()} after building.
*/
protected void setFlow(Flow flow) {
this.flow = flow;
}
public abstract void init(String flowId, AttributeMap attributes) throws FlowBuilderException;
public void buildVariables() throws FlowBuilderException {
}
public void buildInputMapper() throws FlowBuilderException {
}
public void buildStartActions() throws FlowBuilderException {
}
public void buildInlineFlows() throws FlowBuilderException {
}
public abstract void buildStates() throws FlowBuilderException;
public void buildGlobalTransitions() throws FlowBuilderException {
}
public void buildEndActions() throws FlowBuilderException {
}
public void buildOutputMapper() throws FlowBuilderException {
}
public void buildExceptionHandlers() throws FlowBuilderException {
}
/**
* Get the flow (result) built by this builder.
*/
public Flow getFlow() {
return flow;
}
public void dispose() {
setFlow(null);
}
// helpers for use in subclasses
/**
* Returns a conversion executor capable of converting string objects to the target class aliased by the provided
* alias.
* @param targetAlias the target class alias, e.g. "long" or "float"
* @return the conversion executor, or <code>null</code> if no suitable converter exists for given alias
*/
protected ConversionExecutor fromStringTo(String targetAlias) {
return getFlowServiceLocator().getConversionService().getConversionExecutorByTargetAlias(String.class,
targetAlias);
}
/**
* Returns a converter capable of converting a string value to the given type.
* @param targetType the type you wish to convert to (from a string)
* @return the converter
* @throws ConversionException when the converter cannot be found
*/
protected ConversionExecutor fromStringTo(Class targetType) throws ConversionException {
return getFlowServiceLocator().getConversionService().getConversionExecutor(String.class, targetType);
}
}

View File

@@ -1,246 +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.builder;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.convert.support.CompositeConversionService;
import org.springframework.binding.convert.support.DefaultConversionService;
import org.springframework.binding.convert.support.GenericConversionService;
import org.springframework.binding.convert.support.TextToExpression;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.binding.method.TextToMethodSignature;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.webflow.action.BeanInvokingActionFactory;
import org.springframework.webflow.core.DefaultExpressionParserFactory;
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.TargetStateResolver;
import org.springframework.webflow.engine.TransitionCriteria;
import org.springframework.webflow.engine.ViewSelector;
import org.springframework.webflow.execution.Action;
/**
* Base implementation that implements a minimal set of the <code>FlowServiceLocator</code> interface, throwing
* unsupported operation exceptions for some operations.
* <p>
* May be subclassed to offer additional factory/lookup support.
*
* @author Keith Donald
*/
public class BaseFlowServiceLocator implements FlowServiceLocator {
/**
* The factory encapsulating the creation of central Flow artifacts such as {@link Flow flows} and
* {@link State states}.
*/
private FlowArtifactFactory flowArtifactFactory = new FlowArtifactFactory();
/**
* The factory encapsulating the creation of bean invoking actions, actions that adapt methods on objects to the
* {@link Action} interface.
*/
private BeanInvokingActionFactory beanInvokingActionFactory = new BeanInvokingActionFactory();
/**
* The parser for parsing expression strings into evaluatable expression objects.
*/
private ExpressionParser expressionParser = DefaultExpressionParserFactory.getExpressionParser();
/**
* The conversion service configured by the user (none by default).
*/
private ConversionService userConversionService = null;
/**
* A conversion service that can convert between types.
*/
private ConversionService conversionService = createConversionService(userConversionService);
/**
* A resource loader that can load resources.
*/
private ResourceLoader resourceLoader = new DefaultResourceLoader();
/**
* Sets the factory encapsulating the creation of central Flow artifacts such as {@link Flow flows} and
* {@link State states}.
*/
public void setFlowArtifactFactory(FlowArtifactFactory flowArtifactFactory) {
Assert.notNull(flowArtifactFactory, "The flow artifact factory is required");
this.flowArtifactFactory = flowArtifactFactory;
}
/**
* Sets the factory for creating bean invoking actions, actions that adapt methods on objects to the {@link Action}
* interface.
*/
public void setBeanInvokingActionFactory(BeanInvokingActionFactory beanInvokingActionFactory) {
Assert.notNull(beanInvokingActionFactory, "The bean invoking action factory is required");
this.beanInvokingActionFactory = beanInvokingActionFactory;
}
/**
* Set the expression parser responsible for parsing expression strings into evaluatable expression objects.
*/
public void setExpressionParser(ExpressionParser expressionParser) {
Assert.notNull(expressionParser, "The expression parser is required");
this.expressionParser = expressionParser;
// this has impact on the TextToExpression converter in the conversion service!
this.conversionService = createConversionService(userConversionService);
}
/**
* Set the conversion service to use to convert between types; typically from string to a rich object type.
*/
public void setConversionService(ConversionService userConversionService) {
Assert.notNull(userConversionService, "The conversion service is required");
this.userConversionService = userConversionService;
this.conversionService = createConversionService(userConversionService);
}
/**
* Set the resource loader to load file-based resources from string-encoded paths. This is optional.
*/
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public Flow getSubflow(String id) throws FlowArtifactLookupException {
throw new FlowArtifactLookupException(id, Flow.class, "Subflow lookup is not supported by this service locator");
}
public Action getAction(String id) throws FlowArtifactLookupException {
return (Action) getBean(id, Action.class);
}
public FlowAttributeMapper getAttributeMapper(String id) throws FlowArtifactLookupException {
return (FlowAttributeMapper) getBean(id, FlowAttributeMapper.class);
}
public TransitionCriteria getTransitionCriteria(String id) throws FlowArtifactLookupException {
return (TransitionCriteria) getBean(id, TransitionCriteria.class);
}
public TargetStateResolver getTargetStateResolver(String id) throws FlowArtifactLookupException {
return (TargetStateResolver) getBean(id, TargetStateResolver.class);
}
public ViewSelector getViewSelector(String id) throws FlowArtifactLookupException {
return (ViewSelector) getBean(id, ViewSelector.class);
}
public FlowExecutionExceptionHandler getExceptionHandler(String id) throws FlowArtifactLookupException {
return (FlowExecutionExceptionHandler) getBean(id, FlowExecutionExceptionHandler.class);
}
public FlowArtifactFactory getFlowArtifactFactory() {
return flowArtifactFactory;
}
public BeanInvokingActionFactory getBeanInvokingActionFactory() {
return beanInvokingActionFactory;
}
public BeanFactory getBeanFactory() throws UnsupportedOperationException {
throw new UnsupportedOperationException("Bean factory lookup is not supported by this service locator");
}
public ResourceLoader getResourceLoader() {
return resourceLoader;
}
public ExpressionParser getExpressionParser() {
return expressionParser;
}
public ConversionService getConversionService() {
return conversionService;
}
// helpers for use by subclasses
/**
* Helper method for determining if the configured bean factory contains the provided bean.
* @param id the id of the bean
* @return true if yes, false otherwise
*/
protected boolean containsBean(String id) {
return getBeanFactory().containsBean(id);
}
/**
* Helper method to lookup the bean representing a flow artifact of the specified type.
* @param id the bean id
* @param artifactType the bean type
* @return the bean
* @throws FlowArtifactLookupException an exception occurred
*/
protected Object getBean(String id, Class artifactType) throws FlowArtifactLookupException {
try {
return getBeanFactory().getBean(id, artifactType);
} catch (BeansException e) {
throw new FlowArtifactLookupException(id, artifactType, e);
}
}
/**
* Helper method to lookup the type of the bean with the provided id.
* @param id the bean id
* @param artifactType the bean type
* @return the bean's type
* @throws FlowArtifactLookupException an exception occurred
*/
protected Class getBeanType(String id, Class artifactType) throws FlowArtifactLookupException {
try {
return getBeanFactory().getType(id);
} catch (BeansException e) {
throw new FlowArtifactLookupException(id, artifactType, e);
}
}
/**
* Setup a conversion service used by this flow service locator.
* @param userConversionService a user supplied conversion service, optional
* @return the newly created conversion service
*/
protected ConversionService createConversionService(ConversionService userConversionService) {
DefaultConversionService defaultConversionService = new DefaultConversionService();
addWebFlowConverters(defaultConversionService);
if (userConversionService != null) {
return new CompositeConversionService(new ConversionService[] { userConversionService,
defaultConversionService });
} else {
return defaultConversionService;
}
}
/**
* Add all web flow specific converters to given conversion service.
*/
protected void addWebFlowConverters(GenericConversionService conversionService) {
conversionService.addConverter(new TextToTransitionCriteria(this));
conversionService.addConverter(new TextToTargetStateResolver(this));
conversionService.addConverter(new TextToViewSelector(this));
conversionService.addConverter(new TextToExpression(getExpressionParser()));
conversionService.addConverter(new TextToMethodSignature(conversionService));
}
}

View File

@@ -1,95 +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.builder;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.util.Assert;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
import org.springframework.webflow.definition.registry.NoSuchFlowDefinitionException;
import org.springframework.webflow.engine.Flow;
/**
* The default flow service locator implementation that obtains subflow definitions from a dedicated
* {@link FlowDefinitionRegistry} and obtains the remaining services from a generic Spring {@link BeanFactory}.
*
* @see FlowDefinitionRegistry
* @see FlowServiceLocator#getSubflow(String)
* @see BeanFactory
*
* @author Keith Donald
*/
public class DefaultFlowServiceLocator extends BaseFlowServiceLocator {
/**
* The registry for locating subflows.
*/
private FlowDefinitionRegistry subflowRegistry;
/**
* The Spring bean factory used.
*/
private BeanFactory beanFactory;
/**
* Creates a flow service locator that retrieves subflows from the provided registry and additional artifacts from
* the provided bean factory.
* @param subflowRegistry the registry for loading subflows
* @param beanFactory the spring bean factory
*/
public DefaultFlowServiceLocator(FlowDefinitionRegistry subflowRegistry, BeanFactory beanFactory) {
Assert.notNull(subflowRegistry, "The subflow registry is required");
Assert.notNull(beanFactory, "The bean factory is required");
this.subflowRegistry = subflowRegistry;
this.beanFactory = beanFactory;
}
/**
* Convenience flow service locator constructor that looks up a flow definition registry using given bean id in
* given bean factory. The registry is used to retrieve subflows. All additional artifacts are looked up in the
* provided bean factory.
* @param subflowRegistryBeanId the bean id of the subflow FlowDefinitionRegistry
* @param beanFactory the Spring bean factory
* @since 1.0.2
*/
public DefaultFlowServiceLocator(String subflowRegistryBeanId, BeanFactory beanFactory) {
Assert.notNull(subflowRegistryBeanId, "The subflow registry bean id is required");
Assert.notNull(beanFactory, "The bean factory is required");
this.subflowRegistry = (FlowDefinitionRegistry) beanFactory.getBean(subflowRegistryBeanId,
FlowDefinitionRegistry.class);
this.beanFactory = beanFactory;
}
public Flow getSubflow(String id) throws FlowArtifactLookupException {
try {
return (Flow) subflowRegistry.getFlowDefinition(id);
} catch (NoSuchFlowDefinitionException e) {
throw new FlowArtifactLookupException(id, Flow.class, "Could not locate subflow definition with id '" + id
+ "'", e);
}
}
public BeanFactory getBeanFactory() {
return beanFactory;
}
/**
* Returns the flow definition registry used to lookup subflows.
* @return the flow definition registry
*/
protected FlowDefinitionRegistry getSubflowRegistry() {
return subflowRegistry;
}
}

View File

@@ -29,9 +29,9 @@ import org.springframework.webflow.engine.TargetStateResolver;
import org.springframework.webflow.engine.Transition;
import org.springframework.webflow.engine.TransitionCriteria;
import org.springframework.webflow.engine.TransitionableState;
import org.springframework.webflow.engine.ViewSelector;
import org.springframework.webflow.engine.ViewState;
import org.springframework.webflow.execution.Action;
import org.springframework.webflow.execution.ViewFactory;
/**
* A factory for core web flow elements such as {@link Flow flows}, {@link State states}, and
@@ -55,12 +55,9 @@ public class FlowArtifactFactory {
* @param attributes attributes to assign to the Flow, which may also be used to affect flow construction; may be
* null
* @return the initial flow instance, ready for assembly by a FlowBuilder
* @throws FlowArtifactLookupException an exception occured creating the Flow instance
*/
public Flow createFlow(String id, AttributeMap attributes) throws FlowArtifactLookupException {
Flow flow = new Flow(id);
flow.getAttributeMap().putAll(attributes);
return flow;
public Flow createFlow(String id, AttributeMap attributes) {
return Flow.create(id, attributes);
}
/**
@@ -70,7 +67,8 @@ public class FlowArtifactFactory {
* @param id the identifier to assign to the state, must be unique to its owning flow (required)
* @param flow the flow that will own (contain) this state (required)
* @param entryActions any state entry actions; may be null
* @param viewSelector the state view selector strategy; may be null
* @param viewFactory the state view factory strategy
* @param redirect whether to send a flow execution redirect before rendering
* @param renderActions any 'render actions' to execute on entry and refresh; may be null
* @param transitions any transitions (paths) out of this state; may be null
* @param exceptionHandlers any exception handlers; may be null
@@ -78,15 +76,12 @@ public class FlowArtifactFactory {
* @param attributes attributes to assign to the State, which may also be used to affect state construction; may be
* null
* @return the fully initialized view state instance
* @throws FlowArtifactLookupException an exception occured creating the state
*/
public State createViewState(String id, Flow flow, Action[] entryActions, ViewSelector viewSelector,
Action[] renderActions, Transition[] transitions, FlowExecutionExceptionHandler[] exceptionHandlers,
Action[] exitActions, AttributeMap attributes) throws FlowArtifactLookupException {
ViewState viewState = new ViewState(flow, id);
if (viewSelector != null) {
viewState.setViewSelector(viewSelector);
}
public State createViewState(String id, Flow flow, Action[] entryActions, ViewFactory viewFactory,
boolean redirect, Action[] renderActions, Transition[] transitions,
FlowExecutionExceptionHandler[] exceptionHandlers, Action[] exitActions, AttributeMap attributes) {
ViewState viewState = new ViewState(flow, id, viewFactory);
viewState.setRedirect(redirect);
viewState.getRenderActionList().addAll(renderActions);
configureCommonProperties(viewState, entryActions, transitions, exceptionHandlers, exitActions, attributes);
return viewState;
@@ -106,11 +101,10 @@ public class FlowArtifactFactory {
* @param attributes attributes to assign to the State, which may also be used to affect state construction; may be
* null
* @return the fully initialized action state instance
* @throws FlowArtifactLookupException an exception occured creating the state
*/
public State createActionState(String id, Flow flow, Action[] entryActions, Action[] actions,
Transition[] transitions, FlowExecutionExceptionHandler[] exceptionHandlers, Action[] exitActions,
AttributeMap attributes) throws FlowArtifactLookupException {
AttributeMap attributes) {
ActionState actionState = new ActionState(flow, id);
actionState.getActionList().addAll(actions);
configureCommonProperties(actionState, entryActions, transitions, exceptionHandlers, exitActions, attributes);
@@ -130,11 +124,9 @@ public class FlowArtifactFactory {
* @param attributes attributes to assign to the State, which may also be used to affect state construction; may be
* null
* @return the fully initialized decision state instance
* @throws FlowArtifactLookupException an exception occured creating the state
*/
public State createDecisionState(String id, Flow flow, Action[] entryActions, Transition[] transitions,
FlowExecutionExceptionHandler[] exceptionHandlers, Action[] exitActions, AttributeMap attributes)
throws FlowArtifactLookupException {
FlowExecutionExceptionHandler[] exceptionHandlers, Action[] exitActions, AttributeMap attributes) {
DecisionState decisionState = new DecisionState(flow, id);
configureCommonProperties(decisionState, entryActions, transitions, exceptionHandlers, exitActions, attributes);
return decisionState;
@@ -155,12 +147,10 @@ public class FlowArtifactFactory {
* @param attributes attributes to assign to the State, which may also be used to affect state construction; may be
* null
* @return the fully initialized subflow state instance
* @throws FlowArtifactLookupException an exception occured creating the state
*/
public State createSubflowState(String id, Flow flow, Action[] entryActions, Flow subflow,
FlowAttributeMapper attributeMapper, Transition[] transitions,
FlowExecutionExceptionHandler[] exceptionHandlers, Action[] exitActions, AttributeMap attributes)
throws FlowArtifactLookupException {
FlowExecutionExceptionHandler[] exceptionHandlers, Action[] exitActions, AttributeMap attributes) {
SubflowState subflowState = new SubflowState(flow, id, subflow);
if (attributeMapper != null) {
subflowState.setAttributeMapper(attributeMapper);
@@ -176,20 +166,18 @@ public class FlowArtifactFactory {
* @param id the identifier to assign to the state, must be unique to its owning flow (required)
* @param flow the flow that will own (contain) this state (required)
* @param entryActions any state entry actions; may be null
* @param viewSelector the state confirmation view selector strategy; may be null
* @param finalResponseAction the state response renderer; may be null
* @param outputMapper the state output mapper; may be null
* @param exceptionHandlers any exception handlers; may be null
* @param attributes attributes to assign to the State, which may also be used to affect state construction; may be
* null
* @return the fully initialized subflow state instance
* @throws FlowArtifactLookupException an exception occured creating the state
*/
public State createEndState(String id, Flow flow, Action[] entryActions, ViewSelector viewSelector,
AttributeMapper outputMapper, FlowExecutionExceptionHandler[] exceptionHandlers, AttributeMap attributes)
throws FlowArtifactLookupException {
public State createEndState(String id, Flow flow, Action[] entryActions, Action finalResponseAction,
AttributeMapper outputMapper, FlowExecutionExceptionHandler[] exceptionHandlers, AttributeMap attributes) {
EndState endState = new EndState(flow, id);
if (viewSelector != null) {
endState.setViewSelector(viewSelector);
if (finalResponseAction != null) {
endState.setFinalResponseAction(finalResponseAction);
}
if (outputMapper != null) {
endState.setOutputMapper(outputMapper);
@@ -208,10 +196,9 @@ public class FlowArtifactFactory {
* @param attributes attributes to assign to the transition, which may also be used to affect transition
* construction; may be null
* @return the fully initialized transition instance
* @throws FlowArtifactLookupException an exception occured creating the transition
*/
public Transition createTransition(TargetStateResolver targetStateResolver, TransitionCriteria matchingCriteria,
TransitionCriteria executionCriteria, AttributeMap attributes) throws FlowArtifactLookupException {
TransitionCriteria executionCriteria, AttributeMap attributes) {
Transition transition = new Transition(targetStateResolver);
if (matchingCriteria != null) {
transition.setMatchingCriteria(matchingCriteria);

View File

@@ -1,105 +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.builder;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.webflow.core.FlowException;
import org.springframework.webflow.execution.FlowExecutionException;
/**
* A flow artifact lookup exception is thrown when an artifact (such as a flow, state, action, etc.) required by the
* webflow system cannot be obtained.
* <p>
* Flow artifact lookup exceptions indicate unrecoverable problems with the flow definition, e.g. a required action of a
* flow cannot be found. They're not used to signal problems related to execution of a client request. A
* {@link FlowExecutionException} is used for that.
*
* @see org.springframework.webflow.execution.FlowExecutionException
*
* @author Keith Donald
* @author Erwin Vervaet
*/
public class FlowArtifactLookupException extends FlowException {
/**
* The id of the artifact that could not be retrieved.
*/
private String artifactId;
/**
* The type of artifact that could not be retrieved.
*/
private Class artifactType;
/**
* Create a new flow artifact lookup exception.
* @param artifactId the id of the artifact
* @param artifactType the expected artifact type
*/
public FlowArtifactLookupException(String artifactId, Class artifactType) {
this(artifactId, artifactType, null, null);
}
/**
* Create a new flow artifact lookup exception.
* @param artifactId the id of the artifact
* @param artifactType the expected artifact type
* @param cause the underlying cause of this exception
*/
public FlowArtifactLookupException(String artifactId, Class artifactType, Throwable cause) {
this(artifactId, artifactType, null, cause);
}
/**
* Create a new flow artifact lookup exception.
* @param artifactId the id of the artifact
* @param artifactType the expected artifact type
* @param message descriptive message
*/
public FlowArtifactLookupException(String artifactId, Class artifactType, String message) {
this(artifactId, artifactType, message, null);
}
/**
* Create a new flow artifact lookup exception.
* @param artifactId the id of the artifact
* @param artifactType the expected artifact type
* @param message descriptive message
* @param cause the underlying cause of this exception
*/
public FlowArtifactLookupException(String artifactId, Class artifactType, String message, Throwable cause) {
super((StringUtils.hasText(message) ? message : "Unable to obtain a " + ClassUtils.getShortName(artifactType)
+ " flow artifact with id '" + artifactId + "': make sure there is a valid [" + artifactType
+ "] exported with this id"), cause);
this.artifactType = artifactType;
this.artifactId = artifactId;
}
/**
* Returns the id of the artifact that cannot be found.
*/
public String getArtifactId() {
return artifactId;
}
/**
* Returns the expected artifact type.
*/
public Class getArtifactType() {
return artifactType;
}
}

View File

@@ -15,11 +15,7 @@
*/
package org.springframework.webflow.engine.builder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.springframework.webflow.core.collection.AttributeMap;
import org.springframework.webflow.core.collection.CollectionUtils;
import org.springframework.webflow.engine.Flow;
/**
@@ -31,7 +27,7 @@ import org.springframework.webflow.engine.Flow;
*
* <pre>
* FlowBuilder builder = ...;
* Flow flow = new FlowAssembler(&quot;myFlow&quot;, builder).assembleFlow();
* Flow flow = new FlowAssembler(&quot;myFlow&quot;, builder, null).assembleFlow();
* </pre>
*
* @see org.springframework.webflow.engine.builder.FlowBuilder
@@ -41,58 +37,26 @@ import org.springframework.webflow.engine.Flow;
*/
public class FlowAssembler {
private static final Log logger = LogFactory.getLog(FlowAssembler.class);
/**
* The identifier to assign to the flow.
*/
private String flowId;
/**
* Attributes that can be used to affect flow construction.
*/
private AttributeMap flowAttributes;
/**
* The flow builder strategy used to construct the flow from its component parts.
*/
private FlowBuilder flowBuilder;
/**
* Create a new flow assembler that will direct Flow assembly using the specified builder strategy.
* @param flowId the flow id to assign
* @param flowBuilder the builder the factory will use to build flows
* Context needed to initialize the builder so it can perform a build operation.
*/
public FlowAssembler(String flowId, FlowBuilder flowBuilder) {
this(flowId, null, flowBuilder);
}
private FlowBuilderContext flowBuilderContext;
/**
* Create a new flow assembler that will direct Flow assembly using the specified builder strategy.
* @param flowId the flow id to assign
* @param flowAttributes externally assigned flow attributes that can affect flow construction
* @param flowBuilder the builder the factory will use to build flows
* @param flowBuilderContext context to influence the build process
*/
public FlowAssembler(String flowId, AttributeMap flowAttributes, FlowBuilder flowBuilder) {
Assert.hasText(flowId, "The flow id is required");
Assert.notNull(flowBuilder, "The flow builder is required");
this.flowId = flowId;
this.flowAttributes = (flowAttributes != null ? flowAttributes : CollectionUtils.EMPTY_ATTRIBUTE_MAP);
public FlowAssembler(FlowBuilder flowBuilder, FlowBuilderContext flowBuilderContext) {
Assert.notNull(flowBuilder, "A flow builder is required for flow assembly");
Assert.notNull(flowBuilderContext, "A flow builder context is required for flow assembly");
this.flowBuilder = flowBuilder;
}
/**
* Returns the identifier to assign to the flow.
*/
public String getFlowId() {
return flowId;
}
/**
* Returns externally assigned attributes that can be used to affect flow construction.
*/
public AttributeMap getFlowAttributes() {
return flowAttributes;
this.flowBuilderContext = flowBuilderContext;
}
/**
@@ -102,23 +66,27 @@ public class FlowAssembler {
return flowBuilder;
}
/**
* Returns the flow builder context.
* @return flow builder context
*/
public FlowBuilderContext getFlowBuilderContext() {
return flowBuilderContext;
}
/**
* Assembles the flow, directing the construction process by delegating to the configured FlowBuilder. Every call to
* this method will assemble the Flow instance.
* <p>
* This will drive the flow construction process as described in the {@link FlowBuilder} JavaDoc, starting with
* builder initialisation using {@link FlowBuilder#init(String, AttributeMap)} and finishing by cleaning up the
* builder initialization using {@link FlowBuilder#init(FlowBuilderContext)} and finishing by cleaning up the
* builder with a call to {@link FlowBuilder#dispose()}.
* @return the constructed flow
* @throws FlowBuilderException when flow assembly fails
*/
public Flow assembleFlow() throws FlowBuilderException {
if (logger.isDebugEnabled()) {
logger.debug("Assembling flow definition with id '" + flowId + "' using flow builder '" + flowBuilder
+ "'; externally assigned flow attributes are '" + flowAttributes + "'");
}
try {
flowBuilder.init(flowId, flowAttributes);
flowBuilder.init(flowBuilderContext);
directAssembly();
return flowBuilder.getFlow();
} finally {
@@ -134,7 +102,6 @@ public class FlowAssembler {
flowBuilder.buildVariables();
flowBuilder.buildInputMapper();
flowBuilder.buildStartActions();
flowBuilder.buildInlineFlows();
flowBuilder.buildStates();
flowBuilder.buildGlobalTransitions();
flowBuilder.buildEndActions();

View File

@@ -15,18 +15,15 @@
*/
package org.springframework.webflow.engine.builder;
import org.springframework.webflow.core.collection.AttributeMap;
import org.springframework.webflow.engine.Flow;
/**
* Builder interface used to build a flow definition. The process of building a flow consists of the following steps:
* <ol>
* <li> Initialize this builder, creating the initial flow definition, by calling {@link #init(String, AttributeMap)}.
* <li> Initialize this builder, creating the initial flow definition, by calling {@link #init(FlowBuilderContext)}.
* <li> Call {@link #buildVariables()} to create any variables of the flow and add them to the flow definition.
* <li> Call {@link #buildInputMapper()} to create and set the input mapper for the flow.
* <li> Call {@link #buildStartActions()} to create and add any start actions to the flow.
* <li> Call {@link #buildInlineFlows()} to create any inline flows encapsulated by the flow and add them to the flow
* definition.
* <li> Call {@link #buildStates()} to create the states of the flow and add them to the flow definition.
* <li> Call {@link #buildGlobalTransitions()} to create the any transitions shared by all states of the flow and add
* them to the flow definition.
@@ -43,17 +40,14 @@ import org.springframework.webflow.engine.Flow;
* <code>OrderFlowBuilder</code> built in Java code, or a generic flow builder strategy, like the
* <code>XmlFlowBuilder</code>, for building flows from an XML-definition.
* <p>
* Flow builders are used by the {@link org.springframework.webflow.engine.builder.FlowAssembler}, which acts as an
* assembler (director). Flow Builders may be reused, however, exercise caution when doing this as these objects are not
* thread safe. Also, for each use be sure to call init, followed by the build* methods, getFlow, and dispose completely
* in that order.
* Flow builders are used by the {@link FlowAssembler}, which acts as an assembler (director). Flow Builders may be
* reused, however, exercise caution when doing this as these objects are not thread safe. Also, for each use be sure to
* call init, followed by the build* methods, getFlow, and dispose completely in that order.
* <p>
* This is an example of the classic GoF builder pattern.
*
* @see Flow
* @see org.springframework.webflow.engine.builder.FlowAssembler
* @see org.springframework.webflow.engine.builder.AbstractFlowBuilder
* @see org.springframework.webflow.engine.builder.xml.XmlFlowBuilder
* @see FlowAssembler
*
* @author Keith Donald
* @author Erwin Vervaet
@@ -63,63 +57,56 @@ public interface FlowBuilder {
/**
* Initialize this builder. This could cause the builder to open a stream to an externalized resource representing
* the flow definition, for example.
* @param flowId the identifier to assign to the flow
* @param attributes custom attributes to assign to the flow
* @throws FlowBuilderException an exception occured building the flow
* @param context the flow builder context
* @throws FlowBuilderException an exception occurred building the flow
*/
public void init(String flowId, AttributeMap attributes) throws FlowBuilderException;
public void init(FlowBuilderContext context) throws FlowBuilderException;
/**
* Builds any variables initialized by the flow when it starts.
* @throws FlowBuilderException an exception occured building the flow
* @throws FlowBuilderException an exception occurred building the flow
*/
public void buildVariables() throws FlowBuilderException;
/**
* Builds the input mapper responsible for mapping flow input on start.
* @throws FlowBuilderException an exception occured building the flow
* @throws FlowBuilderException an exception occurred building the flow
*/
public void buildInputMapper() throws FlowBuilderException;
/**
* Builds any start actions to execute when the flow starts.
* @throws FlowBuilderException an exception occured building the flow
* @throws FlowBuilderException an exception occurred building the flow
*/
public void buildStartActions() throws FlowBuilderException;
/**
* Builds any "in-line" flows encapsulated by the flow.
* @throws FlowBuilderException an exception occured building the flow
*/
public void buildInlineFlows() throws FlowBuilderException;
/**
* Builds the states of the flow.
* @throws FlowBuilderException an exception occured building the flow
* @throws FlowBuilderException an exception occurred building the flow
*/
public void buildStates() throws FlowBuilderException;
/**
* Builds any transitions shared by all states of the flow.
* @throws FlowBuilderException an exception occured building the flow
* @throws FlowBuilderException an exception occurred building the flow
*/
public void buildGlobalTransitions() throws FlowBuilderException;
/**
* Builds any end actions to execute when the flow ends.
* @throws FlowBuilderException an exception occured building the flow
* @throws FlowBuilderException an exception occurred building the flow
*/
public void buildEndActions() throws FlowBuilderException;
/**
* Builds the output mapper responsible for mapping flow output on end.
* @throws FlowBuilderException an exception occured building the flow
* @throws FlowBuilderException an exception occurred building the flow
*/
public void buildOutputMapper() throws FlowBuilderException;
/**
* Creates and adds all exception handlers to the flow built by this builder.
* @throws FlowBuilderException an exception occured building this flow
* @throws FlowBuilderException an exception occurred building this flow
*/
public void buildExceptionHandlers() throws FlowBuilderException;
@@ -132,7 +119,7 @@ public interface FlowBuilder {
/**
* Shutdown the builder, releasing any resources it holds. A new flow construction process should start with another
* call to the {@link #init(String, AttributeMap)} method.
* call to the {@link #init(FlowBuilderContext)} method.
*/
public void dispose();
}

View File

@@ -0,0 +1,73 @@
package org.springframework.webflow.engine.builder;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.core.io.ResourceLoader;
import org.springframework.webflow.action.BeanInvokingActionFactory;
import org.springframework.webflow.core.collection.AttributeMap;
import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
public interface FlowBuilderContext {
/**
* Returns an externally configured flow definition identifier to assign to the flow being built.
* @return the flow id
*/
public String getFlowId();
/**
* Returns externally configured attributes to assign to the flow definition being built.
* @return the flow attributes
*/
public AttributeMap getFlowAttributes();
/**
* Returns the locator for locating dependent flows (subflows).
* @return the flow definition locator
*/
public FlowDefinitionLocator getFlowDefinitionLocator();
/**
* Returns the factory for core flow artifacts such as Flow and State.
* @return the flow artifact factory
*/
public FlowArtifactFactory getFlowArtifactFactory();
/**
* Returns the factory for bean invoking actions.
* @return the bean invoking action factory
*/
public BeanInvokingActionFactory getBeanInvokingActionFactory();
/**
* Returns the view factory creator for configuring a ViewFactory per view state
* @return the view factory creator
*/
public ViewFactoryCreator getViewFactoryCreator();
/**
* Returns the expression parser for parsing expression strings.
* @return the expression parser
*/
public ExpressionParser getExpressionParser();
/**
* Returns a generic type conversion service for converting between types, typically from string to a rich value
* object.
* @return the generic conversion service
*/
public ConversionService getConversionService();
/**
* Returns a generic resource loader for accessing file-based resources.
* @return the generic resource loader
*/
public ResourceLoader getResourceLoader();
/**
* Returns a generic bean factory for accessing arbitrary services by their id.
* @return the bean factory
*/
public BeanFactory getBeanFactory();
}

View File

@@ -1,112 +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.builder;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistrar;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistryImpl;
import org.springframework.webflow.definition.registry.FlowDefinitionResource;
import org.springframework.webflow.engine.builder.xml.XmlFlowRegistrar;
/**
* A factory bean that accepts an arbitrary list of {@link FlowDefinitionRegistrar}s and uses them to register flows.
* This implementation should not be used programmatically but rather from the spring-webflow-config XML namespace.
* <p>
* Example Usage:
*
* <pre>
* &lt;flow:registry id=&quot;flowRegistry&quot;&gt;
* &lt;flow:location path=&quot;flow1.xml&quot;/&gt;
* &lt;flow:location id=&quot;foo&quot; path=&quot;flow2.xml&quot;/&gt;
* &lt;flow:class name=&quot;BarClass&quot;/&gt;
* &lt;flow:crud entity=&quot;Account&quot;/&gt;
* &lt;flow:namespace name=&quot;baz&quot;&gt;
* &lt;flow:location path=&quot;flow3.xml&quot;/&gt;
* &lt;flow:class name=&quot;XyzClass&quot;/&gt;
* &lt;/flow:namespace&gt;
* &lt;/flow:registry&gt;
* </pre>
*
* @author Ben Hale
*/
public class FlowRegistryFactoryBean implements FactoryBean, InitializingBean, BeanFactoryAware {
private FlowDefinitionRegistry registry;
private BeanFactory beanFactory;
private FlowServiceLocator flowServiceLocator;
private Map xmlNamespaceFlowMappings;
public void setFlowServiceLocator(FlowServiceLocator flowServiceLocator) {
this.flowServiceLocator = flowServiceLocator;
}
public void setXmlNamespaceFlowMappings(Map xmlNamespaceFlowMappings) {
this.xmlNamespaceFlowMappings = xmlNamespaceFlowMappings;
}
public void afterPropertiesSet() throws Exception {
this.registry = new FlowDefinitionRegistryImpl();
initXml(registry);
}
public Object getObject() throws Exception {
return registry;
}
public Class getObjectType() {
return FlowDefinitionRegistry.class;
}
public boolean isSingleton() {
return true;
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
private void initXml(FlowDefinitionRegistry registry) {
XmlFlowRegistrar registrar = new XmlFlowRegistrar(getFlowServiceLocator(registry));
for (Iterator mappings = xmlNamespaceFlowMappings.entrySet().iterator(); mappings.hasNext();) {
Map.Entry mapping = (Map.Entry) mappings.next();
String namespace = (String) mapping.getKey();
for (Iterator resources = ((Set) mapping.getValue()).iterator(); resources.hasNext();) {
FlowDefinitionResource resource = (FlowDefinitionResource) resources.next();
registrar.addResource(resource, namespace);
}
}
registrar.registerFlowDefinitions(registry);
}
private FlowServiceLocator getFlowServiceLocator(FlowDefinitionRegistry registry) {
if (flowServiceLocator == null) {
this.flowServiceLocator = new DefaultFlowServiceLocator(registry, beanFactory);
}
return flowServiceLocator;
}
}

View File

@@ -1,58 +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.builder;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import junit.framework.TestCase;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
import org.springframework.webflow.definition.registry.FlowDefinitionResource;
import org.springframework.webflow.test.MockFlowServiceLocator;
/**
* Tests that the factory bean properly creates a {@link FlowDefinitionRegistry} with the proper definitions in it.
*/
public class FlowRegistryFactoryBeanTests extends TestCase {
public void testXmlRegistrar() throws Exception {
Set emptyNamespace = new HashSet();
emptyNamespace.add(new FlowDefinitionResource(fromClassPath("flow1.xml")));
Set bookingNamespace = new HashSet();
bookingNamespace.add(new FlowDefinitionResource(fromClassPath("flow2.xml")));
Map xmlNamespaceFlowMappings = new HashMap();
xmlNamespaceFlowMappings.put("", emptyNamespace);
xmlNamespaceFlowMappings.put("booking", bookingNamespace);
FlowRegistryFactoryBean factoryBean = new FlowRegistryFactoryBean();
factoryBean.setFlowServiceLocator(new MockFlowServiceLocator());
factoryBean.setXmlNamespaceFlowMappings(xmlNamespaceFlowMappings);
factoryBean.afterPropertiesSet();
FlowDefinitionRegistry registry = (FlowDefinitionRegistry) factoryBean.getObject();
assertEquals("Incorrect number of flows", 2, registry.getFlowDefinitionCount());
assertTrue("Missing flow", registry.containsFlowDefinition("flow1"));
assertTrue("Missing flow", registry.containsFlowDefinition("booking/flow2"));
}
private Resource fromClassPath(String resourceName) {
return new ClassPathResource(resourceName, getClass());
}
}

View File

@@ -1,152 +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.builder;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.core.io.ResourceLoader;
import org.springframework.webflow.action.AbstractBeanInvokingAction;
import org.springframework.webflow.action.BeanInvokingActionFactory;
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.TargetStateResolver;
import org.springframework.webflow.engine.Transition;
import org.springframework.webflow.engine.TransitionCriteria;
import org.springframework.webflow.engine.ViewSelector;
import org.springframework.webflow.execution.Action;
/**
* A support interface used by flow builders at configuration time. Acts as a "service locator" responsible for:
* <ol>
* <li> Retrieving dependent (but externally managed) flow services needed to configure flow and state definitions. Such
* services are usually hosted in a backing registry and may be shared by multiple flows.
* <li> Providing access to abstract factories to create core flow definitional artifacts such as {@link Flow},
* {@link State}, {@link Transition}, and {@link AbstractBeanInvokingAction bean invoking actions}. These artifacts
* are unique to each flow and are typically not shared.
* </ol>
* <p>
* In general, implementations of this interface act as facades to accessing and creating flow artifacts during
* {@link FlowAssembler flow assembly}.
* <p>
* Finally, this interface also exposes access to generic infrastructure services also needed by flow assemblers such as
* a {@link ConversionService} and {@link ExpressionParser}.
*
* @see org.springframework.webflow.engine.builder.FlowBuilder
* @see org.springframework.webflow.engine.builder.BaseFlowBuilder
* @see org.springframework.webflow.engine.builder.FlowAssembler
*
* @author Keith Donald
* @author Erwin Vervaet
*/
public interface FlowServiceLocator {
/**
* Returns the Flow to be used as a subflow with the provided id.
* @param id the flow id
* @return the flow to be used as a subflow
* @throws FlowArtifactLookupException when no such flow is found
*/
public Flow getSubflow(String id) throws FlowArtifactLookupException;
/**
* Retrieve an action to be executed within a flow with the assigned id.
* @param id the id of the action
* @throws FlowArtifactLookupException when no such action is found
*/
public Action getAction(String id) throws FlowArtifactLookupException;
/**
* Returns the flow attribute mapper with the provided id. Flow attribute mappers are used from subflow states to
* map input and output attributes.
* @param id the attribute mapper id
* @return the attribute mapper
* @throws FlowArtifactLookupException when no such mapper is found
*/
public FlowAttributeMapper getAttributeMapper(String id) throws FlowArtifactLookupException;
/**
* Returns the transition criteria to drive state transitions with the provided id.
* @param id the transition criteria id
* @return the transition criteria
* @throws FlowArtifactLookupException when no such criteria is found
*/
public TransitionCriteria getTransitionCriteria(String id) throws FlowArtifactLookupException;
/**
* Returns the transition target state resolver with the specified id.
* @param id the target state resolver id
* @return the target state resolver
* @throws FlowArtifactLookupException when no such resolver is found
*/
public TargetStateResolver getTargetStateResolver(String id) throws FlowArtifactLookupException;
/**
* Returns the view selector to make view selections in view states with the provided id.
* @param id the view selector id
* @return the view selector
* @throws FlowArtifactLookupException when no such selector is found
*/
public ViewSelector getViewSelector(String id) throws FlowArtifactLookupException;
/**
* Returns the exception handler to handle flow execution exceptions with the provided id.
* @param id the exception handler id
* @return the exception handler
* @throws FlowArtifactLookupException when no such handler is found
*/
public FlowExecutionExceptionHandler getExceptionHandler(String id) throws FlowArtifactLookupException;
/**
* Returns the factory for core flow artifacts such as Flow and State.
* @return the flow artifact factory
*/
public FlowArtifactFactory getFlowArtifactFactory();
/**
* Returns the factory for bean invoking actions.
* @return the bean invoking action factory
*/
public BeanInvokingActionFactory getBeanInvokingActionFactory();
/**
* Returns a generic bean (service) registry for accessing arbitrary beans.
* @return the generic service registry
* @throws UnsupportedOperationException when not supported by this locator
*/
public BeanFactory getBeanFactory() throws UnsupportedOperationException;
/**
* Returns a generic resource loader for accessing file-based resources.
* @return the generic resource loader
*/
public ResourceLoader getResourceLoader();
/**
* Returns the expression parser for parsing expression strings.
* @return the expression parser
*/
public ExpressionParser getExpressionParser();
/**
* Returns a generic type conversion service for converting between types, typically from string to a rich value
* object.
* @return the generic conversion service
*/
public ConversionService getConversionService();
}

View File

@@ -23,7 +23,6 @@ import org.springframework.core.io.Resource;
import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.webflow.definition.registry.FlowDefinitionConstructionException;
import org.springframework.webflow.definition.registry.FlowDefinitionHolder;
import org.springframework.webflow.engine.Flow;
import org.springframework.webflow.util.ResourceHolder;
/**
@@ -32,13 +31,11 @@ import org.springframework.webflow.util.ResourceHolder;
* <p>
* This class is thread-safe.
* <p>
* Note that this {@link FlowDefinition} holder uses a {@link Flow} assembler. This is normal since a {@link Flow} is a
* {@link FlowDefinition}! This class bridges the <i>abstract</i> world of {@link FlowDefinition flow definitions}
* with the <i>concrete</i> world of {@link Flow flow implementations}.
* Note that this {@link FlowDefinition} holder uses a {@link FlowAssembler}. This class bridges the <i>abstract</i>
* world of {@link FlowDefinition flow definitions} with the <i>concrete</i> world of flow implementations.
*
* @see FlowDefinition
* @see Flow
* @see FlowAssembler
* @see FlowDefinition
*
* @author Keith Donald
*/
@@ -77,7 +74,7 @@ public class RefreshableFlowDefinitionHolder implements FlowDefinitionHolder {
}
public String getFlowDefinitionId() {
return assembler.getFlowId();
return assembler.getFlowBuilderContext().getFlowId();
}
public synchronized FlowDefinition getFlowDefinition() throws FlowDefinitionConstructionException {
@@ -85,8 +82,9 @@ public class RefreshableFlowDefinitionHolder implements FlowDefinitionHolder {
// must return early assembly result
return getFlowBuilder().getFlow();
}
if (!isAssembled()) {
if (flowDefinition == null) {
lastModified = calculateLastModified();
logger.debug("Assembling the flow definition for the first time");
assembleFlow();
} else {
refreshIfChanged();
@@ -94,91 +92,66 @@ public class RefreshableFlowDefinitionHolder implements FlowDefinitionHolder {
return flowDefinition;
}
public synchronized void refresh() throws FlowBuilderException {
public synchronized void refresh() throws FlowDefinitionConstructionException {
assembleFlow();
}
// internal helpers
/**
* Returns the flow builder that actually builds the Flow definition.
*/
protected FlowBuilder getFlowBuilder() {
return assembler.getFlowBuilder();
}
/**
* Reassemble the flow if its underlying resource has changed.
*/
protected void refreshIfChanged() {
if (this.lastModified == -1) {
// just ignore, tracking last modified date not supported
return;
}
long calculatedLastModified = calculateLastModified();
if (this.lastModified < calculatedLastModified) {
if (logger.isDebugEnabled()) {
logger.debug("Resource modification detected, reloading flow definition with id '"
+ assembler.getFlowId() + "'");
}
assembleFlow();
this.lastModified = calculatedLastModified;
}
}
/**
* Helper that retrieves the last modified date by querying the backing flow resource.
* @return the last modified date, or -1 if it could not be retrieved
* @return the last modified date, or 0L if it could not be retrieved
*/
protected long calculateLastModified() {
private long calculateLastModified() {
if (getFlowBuilder() instanceof ResourceHolder) {
Resource resource = ((ResourceHolder) getFlowBuilder()).getResource();
if (logger.isDebugEnabled()) {
logger.debug("Calculating last modified timestamp for flow definition resource '" + resource + "'");
}
try {
return resource.getFile().lastModified();
long lastModified = resource.getFile().lastModified();
if (logger.isDebugEnabled()) {
logger.debug("Flow definition [" + resource + "] was last modified on " + lastModified);
}
return lastModified;
} catch (IOException e) {
// ignore, last modified checks not supported
}
}
return -1;
}
/**
* Returns the last modified date of the backed flow definition resource.
* @return the last modified date
*/
protected long getLastModified() {
return lastModified;
return 0L;
}
/**
* Assemble the held flow definition, delegating to the configured FlowAssembler (director).
*/
protected void assembleFlow() throws FlowBuilderException {
if (logger.isDebugEnabled()) {
logger.debug("Assembling flow definition with id '" + assembler.getFlowId() + "'");
}
private void assembleFlow() throws FlowDefinitionConstructionException {
try {
assembling = true;
flowDefinition = assembler.assembleFlow();
} catch (FlowBuilderException e) {
throw new FlowDefinitionConstructionException(assembler.getFlowBuilderContext().getFlowId(), e);
} finally {
assembling = false;
}
}
/**
* Returns a flag indicating if this holder has performed and completed flow definition assembly.
* Reassemble the flow if its underlying resource has changed.
*/
protected boolean isAssembled() {
return flowDefinition != null;
private void refreshIfChanged() {
long calculatedLastModified = calculateLastModified();
if (calculatedLastModified > lastModified) {
assembleFlow();
lastModified = calculatedLastModified;
}
}
/**
* Returns a flag indicating if this holder is performing assembly.
* Returns the flow builder that actually builds the Flow definition.
*/
protected boolean isAssembling() {
return assembling;
private FlowBuilder getFlowBuilder() {
return assembler.getFlowBuilder();
}
public String toString() {
return "'" + getFlowDefinitionId() + "'";
}
}

View File

@@ -1,138 +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.builder;
import org.springframework.binding.convert.ConversionContext;
import org.springframework.binding.convert.support.ConversionServiceAwareConverter;
import org.springframework.binding.expression.Expression;
import org.springframework.util.StringUtils;
import org.springframework.webflow.engine.NullViewSelector;
import org.springframework.webflow.engine.ViewSelector;
import org.springframework.webflow.engine.support.ApplicationViewSelector;
import org.springframework.webflow.engine.support.ExternalRedirectSelector;
import org.springframework.webflow.engine.support.FlowDefinitionRedirectSelector;
import org.springframework.webflow.execution.support.ApplicationView;
import org.springframework.webflow.execution.support.ExternalRedirect;
import org.springframework.webflow.execution.support.FlowDefinitionRedirect;
import org.springframework.webflow.execution.support.FlowExecutionRedirect;
/**
* Converter that converts an encoded string representation of a view selector into a {@link ViewSelector} object that
* will make selections at runtime.
* <p>
* This converter supports the following encoded forms:
* <ul>
* <li>empty - will result in a {@link NullViewSelector}.</li>
* <li>"viewName" - will result in an {@link ApplicationViewSelector} that returns an {@link ApplicationView}
* ViewSelection with the provided view name expression.</li>
* <li>"redirect:&lt;viewName&gt;" - will result in an {@link ApplicationViewSelector} that returns an
* {@link FlowExecutionRedirect} to a flow execution URL.</li>
* <li>"externalRedirect:&lt;url&gt;" - will result in an {@link ExternalRedirectSelector} that returns an
* {@link ExternalRedirect} to a URL.</li>
* <li>"flowRedirect:&lt;flowId&gt;" - will result in a {@link FlowDefinitionRedirectSelector} that returns a
* {@link FlowDefinitionRedirect} to a flow.</li>
* <li>"bean:&lt;id&gt;" - will result in usage of a custom <code>ViewSelector</code> bean implementation.</li>
* </ul>
*
* @see org.springframework.webflow.execution.ViewSelection
* @see org.springframework.webflow.engine.ViewSelector
*
* @author Keith Donald
* @author Erwin Vervaet
*/
public class TextToViewSelector extends ConversionServiceAwareConverter {
/**
* Prefix used when the encoded view name wants to specify that a redirect is required. ("redirect:")
*/
public static final String REDIRECT_PREFIX = "redirect:";
/**
* Prefix used when the encoded view name wants to specify that a redirect to an external URL is required.
* ("externalRedirect:")
*/
public 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:")
*/
public 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:";
/**
* Locator to use for loading custom ViewSelector beans.
*/
private FlowServiceLocator flowServiceLocator;
/**
* Create a new text to ViewSelector converter. Custom ViewSelector implemenations will be looked up using given
* service locator.
*/
public TextToViewSelector(FlowServiceLocator flowServiceLocator) {
this.flowServiceLocator = flowServiceLocator;
setConversionService(flowServiceLocator.getConversionService());
}
public Class[] getSourceClasses() {
return new Class[] { String.class };
}
public Class[] getTargetClasses() {
return new Class[] { ViewSelector.class };
}
protected Object doConvert(Object source, Class targetClass, ConversionContext context) throws Exception {
String encodedView = (String) source;
if (!StringUtils.hasText(encodedView)) {
return NullViewSelector.INSTANCE;
} else {
return convertEncodedViewSelector(encodedView);
}
}
/**
* Convert given encoded view into an appropriate view selector.
* @param encodedView the encoded view selector
* @return the view selector
*/
protected ViewSelector convertEncodedViewSelector(String encodedView) {
if (encodedView.startsWith(REDIRECT_PREFIX)) {
String viewName = encodedView.substring(REDIRECT_PREFIX.length());
Expression viewNameExpr = (Expression) fromStringTo(Expression.class).execute(viewName);
// just show the application view using a redirect
return new ApplicationViewSelector(viewNameExpr, true);
} else if (encodedView.startsWith(EXTERNAL_REDIRECT_PREFIX)) {
String externalUrl = encodedView.substring(EXTERNAL_REDIRECT_PREFIX.length());
Expression urlExpr = (Expression) fromStringTo(Expression.class).execute(externalUrl);
return new ExternalRedirectSelector(urlExpr);
} else if (encodedView.startsWith(FLOW_DEFINITION_REDIRECT_PREFIX)) {
String flowRedirect = encodedView.substring(FLOW_DEFINITION_REDIRECT_PREFIX.length());
Expression redirectExpr = (Expression) fromStringTo(Expression.class).execute(flowRedirect);
return new FlowDefinitionRedirectSelector(redirectExpr);
} else if (encodedView.startsWith(BEAN_PREFIX)) {
String id = encodedView.substring(BEAN_PREFIX.length());
return flowServiceLocator.getViewSelector(id);
} else {
Expression viewNameExpr = (Expression) fromStringTo(Expression.class).execute(encodedView);
return new ApplicationViewSelector(viewNameExpr);
}
}
}

View File

@@ -0,0 +1,13 @@
package org.springframework.webflow.engine.builder;
import org.springframework.binding.expression.Expression;
import org.springframework.webflow.execution.Action;
import org.springframework.webflow.execution.ViewFactory;
public interface ViewFactoryCreator {
public ViewFactory createViewFactory(Expression viewId);
public Action createFinalResponseAction(Expression viewId);
}

View File

@@ -0,0 +1,97 @@
/*
* 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.builder.support;
import org.springframework.webflow.core.collection.AttributeMap;
import org.springframework.webflow.engine.Flow;
import org.springframework.webflow.engine.builder.FlowBuilder;
import org.springframework.webflow.engine.builder.FlowBuilderContext;
import org.springframework.webflow.engine.builder.FlowBuilderException;
/**
* Abstract base implementation of a flow builder defining common functionality needed by most concrete flow builder
* implementations. This class implements all optional parts of the FlowBuilder process as no-op methods. Subclasses are
* only required to implement {@link #buildStates()}.
*
* @author Keith Donald
* @author Erwin Vervaet
*/
public abstract class AbstractFlowBuilder implements FlowBuilder {
/**
* The <code>Flow</code> built by this builder.
*/
private Flow flow;
private FlowBuilderContext context;
protected FlowBuilderContext getContext() {
return context;
}
public void init(FlowBuilderContext context) throws FlowBuilderException {
this.context = context;
doInit();
this.flow = createFlow();
}
protected void doInit() {
}
protected Flow createFlow() {
String id = getContext().getFlowId();
AttributeMap attributes = getContext().getFlowAttributes();
return getContext().getFlowArtifactFactory().createFlow(id, attributes);
}
public void buildVariables() throws FlowBuilderException {
}
public void buildInputMapper() throws FlowBuilderException {
}
public void buildStartActions() throws FlowBuilderException {
}
public abstract void buildStates() throws FlowBuilderException;
public void buildGlobalTransitions() throws FlowBuilderException {
}
public void buildEndActions() throws FlowBuilderException {
}
public void buildOutputMapper() throws FlowBuilderException {
}
public void buildExceptionHandlers() throws FlowBuilderException {
}
public Flow getFlow() {
return flow;
}
public void dispose() {
flow = null;
doDispose();
}
protected void doDispose() {
}
}

View File

@@ -0,0 +1,50 @@
package org.springframework.webflow.engine.builder.support;
import org.springframework.webflow.execution.Action;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.View;
import org.springframework.webflow.execution.ViewFactory;
public class ActionInvokingViewFactory implements ViewFactory {
private Action action;
public ActionInvokingViewFactory(Action action) {
this.action = action;
}
public View getView(RequestContext context) {
return new ActionExecutingView(action, context);
}
private static class ActionExecutingView implements View {
private Action action;
private RequestContext context;
private ActionExecutingView(Action action, RequestContext context) {
this.action = action;
this.context = context;
}
public boolean eventSignaled() {
return context.getExternalContext().getRequestParameterMap().contains("_eventId");
}
public Event getEvent() {
return new Event(this, context.getExternalContext().getRequestParameterMap().get("_eventId"));
}
public void render() {
try {
action.execute(context);
} catch (Exception e) {
// TODO
}
}
}
}

View File

@@ -0,0 +1,82 @@
package org.springframework.webflow.engine.builder.support;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.convert.support.GenericConversionService;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.core.io.ResourceLoader;
import org.springframework.webflow.action.BeanInvokingActionFactory;
import org.springframework.webflow.core.collection.AttributeMap;
import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
import org.springframework.webflow.engine.builder.FlowArtifactFactory;
import org.springframework.webflow.engine.builder.FlowBuilderContext;
import org.springframework.webflow.engine.builder.ViewFactoryCreator;
public class FlowBuilderContextImpl implements FlowBuilderContext {
private String flowId;
private AttributeMap flowAttributes;
private FlowDefinitionLocator flowDefinitionLocator;
private FlowBuilderServices flowBuilderServices;
private GenericConversionService flowConversionService;
public FlowBuilderContextImpl(String flowId, AttributeMap flowAttributes,
FlowDefinitionLocator flowDefinitionLocator, FlowBuilderServices flowBuilderServices) {
this.flowId = flowId;
this.flowAttributes = flowAttributes;
this.flowDefinitionLocator = flowDefinitionLocator;
this.flowBuilderServices = flowBuilderServices;
flowConversionService = new GenericConversionService();
flowConversionService.addConverter(new TextToTransitionCriteria(this));
flowConversionService.addConverter(new TextToTargetStateResolver(this));
flowConversionService.setParent(this.flowBuilderServices.getConversionService());
}
public String getFlowId() {
return flowId;
}
public AttributeMap getFlowAttributes() {
return flowAttributes;
}
public FlowArtifactFactory getFlowArtifactFactory() {
return flowBuilderServices.getFlowArtifactFactory();
}
public BeanInvokingActionFactory getBeanInvokingActionFactory() {
return flowBuilderServices.getBeanInvokingActionFactory();
}
public ViewFactoryCreator getViewFactoryCreator() {
return flowBuilderServices.getViewFactoryCreator();
}
public ExpressionParser getExpressionParser() {
return flowBuilderServices.getExpressionParser();
}
public ConversionService getConversionService() {
return flowConversionService;
}
public ResourceLoader getResourceLoader() {
return flowBuilderServices.getResourceLoader();
}
public BeanFactory getBeanFactory() {
return flowBuilderServices.getBeanFactory();
}
public FlowDefinitionLocator getFlowDefinitionLocator() {
return flowDefinitionLocator;
}
public FlowBuilderServices getFlowBuilderServices() {
return flowBuilderServices;
}
}

View File

@@ -0,0 +1,121 @@
package org.springframework.webflow.engine.builder.support;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.convert.support.DefaultConversionService;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.webflow.action.BeanInvokingActionFactory;
import org.springframework.webflow.core.expression.DefaultExpressionParserFactory;
import org.springframework.webflow.engine.Flow;
import org.springframework.webflow.engine.State;
import org.springframework.webflow.engine.builder.FlowArtifactFactory;
import org.springframework.webflow.engine.builder.ViewFactoryCreator;
import org.springframework.webflow.execution.Action;
public class FlowBuilderServices implements ResourceLoaderAware, BeanFactoryAware {
/**
* The factory encapsulating the creation of central Flow artifacts such as {@link Flow flows} and
* {@link State states}.
*/
private FlowArtifactFactory flowArtifactFactory = new FlowArtifactFactory();
/**
* The factory encapsulating the creation of bean invoking actions, actions that adapt methods on objects to the
* {@link Action} interface.
*/
private BeanInvokingActionFactory beanInvokingActionFactory = new BeanInvokingActionFactory();
/**
* The view factory creator.
*/
private ViewFactoryCreator viewFactoryCreator;
/**
* The conversion service.
*/
private ConversionService conversionService = new DefaultConversionService();
/**
* The parser for parsing expression strings into expression objects.
*/
private ExpressionParser expressionParser = DefaultExpressionParserFactory.getExpressionParser();
/**
* A resource loader that can load resources.
*/
private ResourceLoader resourceLoader;
/**
* The Spring bean factory used.
*/
private BeanFactory beanFactory;
public FlowArtifactFactory getFlowArtifactFactory() {
return flowArtifactFactory;
}
public void setFlowArtifactFactory(FlowArtifactFactory flowArtifactFactory) {
Assert.notNull(flowArtifactFactory, "The flow artifact factory is required");
this.flowArtifactFactory = flowArtifactFactory;
}
public BeanInvokingActionFactory getBeanInvokingActionFactory() {
return beanInvokingActionFactory;
}
public void setBeanInvokingActionFactory(BeanInvokingActionFactory beanInvokingActionFactory) {
Assert.notNull(beanInvokingActionFactory, "The bean invoking action factory is required");
this.beanInvokingActionFactory = beanInvokingActionFactory;
}
public ViewFactoryCreator getViewFactoryCreator() {
return viewFactoryCreator;
}
public void setViewFactoryCreator(ViewFactoryCreator viewFactoryCreator) {
Assert.notNull("The view factory creator cannot be null");
this.viewFactoryCreator = viewFactoryCreator;
}
public ConversionService getConversionService() {
return conversionService;
}
public void setConversionService(ConversionService conversionService) {
Assert.notNull(conversionService, "The type conversion service cannot be null");
this.conversionService = conversionService;
}
public ExpressionParser getExpressionParser() {
return expressionParser;
}
public void setExpressionParser(ExpressionParser expressionParser) {
Assert.notNull(expressionParser, "The expression parser cannot be null");
this.expressionParser = expressionParser;
}
public ResourceLoader getResourceLoader() {
return resourceLoader;
}
public void setResourceLoader(ResourceLoader resourceLoader) {
Assert.notNull("The resource loader cannot be null");
this.resourceLoader = resourceLoader;
}
public BeanFactory getBeanFactory() {
return beanFactory;
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
Assert.notNull("The bean factory cannot be null");
this.beanFactory = beanFactory;
}
}

View File

@@ -13,13 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.webflow.engine.builder;
package org.springframework.webflow.engine.builder.support;
import org.springframework.binding.convert.ConversionContext;
import org.springframework.binding.convert.support.AbstractConverter;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.webflow.engine.TargetStateResolver;
import org.springframework.webflow.engine.builder.FlowBuilderContext;
import org.springframework.webflow.engine.support.DefaultTargetStateResolver;
import org.springframework.webflow.execution.RequestContext;
/**
* Converter that takes an encoded string representation and produces a corresponding {@link TargetStateResolver}
@@ -37,7 +40,7 @@ import org.springframework.webflow.engine.support.DefaultTargetStateResolver;
* @author Keith Donald
* @author Erwin Vervaet
*/
public class TextToTargetStateResolver extends AbstractConverter {
class TextToTargetStateResolver extends AbstractConverter {
/**
* Prefix used when the user wants to use a custom TargetStateResolver implementation managed by a factory.
@@ -45,16 +48,16 @@ public class TextToTargetStateResolver extends AbstractConverter {
private static final String BEAN_PREFIX = "bean:";
/**
* Locator to use for loading custom TargetStateResolver beans.
* Context for flow builder services.
*/
private FlowServiceLocator flowServiceLocator;
private FlowBuilderContext flowBuilderContext;
/**
* Create a new converter that converts strings to transition target state resolver objects. The given conversion
* service will be used to do all necessary internal conversion (e.g. parsing expression strings).
*/
public TextToTargetStateResolver(FlowServiceLocator flowServiceLocator) {
this.flowServiceLocator = flowServiceLocator;
public TextToTargetStateResolver(FlowBuilderContext flowBuilderContext) {
this.flowBuilderContext = flowBuilderContext;
}
public Class[] getSourceClasses() {
@@ -67,11 +70,12 @@ public class TextToTargetStateResolver extends AbstractConverter {
protected Object doConvert(Object source, Class targetClass, ConversionContext context) throws Exception {
String targetStateId = (String) source;
if (flowServiceLocator.getExpressionParser().isDelimitedExpression(targetStateId)) {
Expression expression = flowServiceLocator.getExpressionParser().parseExpression(targetStateId);
ExpressionParser parser = flowBuilderContext.getExpressionParser();
if (parser.isEvalExpressionString(targetStateId)) {
Expression expression = parser.parseExpression(targetStateId, RequestContext.class, String.class, null);
return new DefaultTargetStateResolver(expression);
} else if (targetStateId.startsWith(BEAN_PREFIX)) {
return flowServiceLocator.getTargetStateResolver(targetStateId.substring(BEAN_PREFIX.length()));
return flowBuilderContext.getBeanFactory().getBean(targetStateId.substring(BEAN_PREFIX.length()));
} else {
return new DefaultTargetStateResolver(targetStateId);
}

View File

@@ -13,17 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.webflow.engine.builder;
package org.springframework.webflow.engine.builder.support;
import org.springframework.binding.convert.ConversionContext;
import org.springframework.binding.convert.ConversionException;
import org.springframework.binding.convert.support.AbstractConverter;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.binding.expression.ExpressionVariable;
import org.springframework.util.StringUtils;
import org.springframework.webflow.engine.TransitionCriteria;
import org.springframework.webflow.engine.WildcardTransitionCriteria;
import org.springframework.webflow.engine.builder.FlowBuilderContext;
import org.springframework.webflow.engine.support.BooleanExpressionTransitionCriteria;
import org.springframework.webflow.engine.support.EventIdTransitionCriteria;
import org.springframework.webflow.execution.RequestContext;
/**
* Converter that takes an encoded string representation and produces a corresponding <code>TransitionCriteria</code>
@@ -45,7 +49,7 @@ import org.springframework.webflow.engine.support.EventIdTransitionCriteria;
* @author Keith Donald
* @author Erwin Vervaet
*/
public class TextToTransitionCriteria extends AbstractConverter {
class TextToTransitionCriteria extends AbstractConverter {
/**
* Prefix used when the user wants to use a custom TransitionCriteria implementation managed by a bean factory.
@@ -53,16 +57,16 @@ public class TextToTransitionCriteria extends AbstractConverter {
private static final String BEAN_PREFIX = "bean:";
/**
* Locator to use for loading custom TransitionCriteria beans.
* Context for flow builder services.
*/
private FlowServiceLocator flowServiceLocator;
private FlowBuilderContext flowBuilderContext;
/**
* Create a new converter that converts strings to transition criteria objects. Custom transition criteria will be
* looked up using given service locator.
*/
public TextToTransitionCriteria(FlowServiceLocator flowServiceLocator) {
this.flowServiceLocator = flowServiceLocator;
public TextToTransitionCriteria(FlowBuilderContext flowBuilderContext) {
this.flowBuilderContext = flowBuilderContext;
}
public Class[] getSourceClasses() {
@@ -75,14 +79,18 @@ public class TextToTransitionCriteria extends AbstractConverter {
protected Object doConvert(Object source, Class targetClass, ConversionContext context) throws Exception {
String encodedCriteria = (String) source;
ExpressionParser parser = flowBuilderContext.getExpressionParser();
if (!StringUtils.hasText(encodedCriteria)
|| WildcardTransitionCriteria.WILDCARD_EVENT_ID.equals(encodedCriteria)) {
return WildcardTransitionCriteria.INSTANCE;
} else if (flowServiceLocator.getExpressionParser().isDelimitedExpression(encodedCriteria)) {
Expression expression = flowServiceLocator.getExpressionParser().parseExpression(encodedCriteria);
} else if (parser.isEvalExpressionString(encodedCriteria)) {
ExpressionVariable[] variables = new ExpressionVariable[] { new ExpressionVariable("result", "lastEvent.id") };
Expression expression = parser.parseExpression(encodedCriteria, RequestContext.class, Boolean.class,
variables);
return createBooleanExpressionTransitionCriteria(expression);
} else if (encodedCriteria.startsWith(BEAN_PREFIX)) {
return flowServiceLocator.getTransitionCriteria(encodedCriteria.substring(BEAN_PREFIX.length()));
return flowBuilderContext.getBeanFactory().getBean(encodedCriteria.substring(BEAN_PREFIX.length()),
TransitionCriteria.class);
} else {
return createEventIdTransitionCriteria(encodedCriteria);
}

View File

@@ -87,7 +87,6 @@ public class DefaultDocumentLoader implements DocumentLoader {
/**
* Set a SAX entity resolver to be used for parsing. Can be overridden for custom entity resolution, for example
* relative to some specific base path.
* @see org.springframework.webflow.engine.builder.xml.WebFlowEntityResolver
*/
public void setEntityResolver(EntityResolver entityResolver) {
this.entityResolver = entityResolver;

View File

@@ -0,0 +1,100 @@
/*
* 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.builder.xml;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.core.io.ResourceLoader;
import org.springframework.webflow.action.BeanInvokingActionFactory;
import org.springframework.webflow.core.collection.AttributeMap;
import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
import org.springframework.webflow.engine.builder.FlowArtifactFactory;
import org.springframework.webflow.engine.builder.FlowBuilderContext;
import org.springframework.webflow.engine.builder.ViewFactoryCreator;
class LocalFlowBuilderContext implements FlowBuilderContext {
private FlowBuilderContext parent;
private BeanFactory localFlowBeanFactory;
public LocalFlowBuilderContext(FlowBuilderContext parent, BeanFactory localFlowBeanFactory) {
this.parent = parent;
this.localFlowBeanFactory = localFlowBeanFactory;
}
public String getFlowId() {
return parent.getFlowId();
}
public AttributeMap getFlowAttributes() {
return parent.getFlowAttributes();
}
public FlowDefinitionLocator getFlowDefinitionLocator() {
return (FlowDefinitionLocator) localFlowBeanFactory.getBean("flowRegistry", FlowDefinitionLocator.class);
}
public FlowArtifactFactory getFlowArtifactFactory() {
if (localFlowBeanFactory.containsBean("flowArtifactFactory")) {
return (FlowArtifactFactory) localFlowBeanFactory.getBean("flowArtifactFactory", FlowArtifactFactory.class);
} else {
return parent.getFlowArtifactFactory();
}
}
public BeanInvokingActionFactory getBeanInvokingActionFactory() {
if (localFlowBeanFactory.containsBean("beanInvokingActionFactory")) {
return (BeanInvokingActionFactory) localFlowBeanFactory.getBean("beanInvokingActionFactory",
BeanInvokingActionFactory.class);
} else {
return parent.getBeanInvokingActionFactory();
}
}
public ViewFactoryCreator getViewFactoryCreator() {
if (localFlowBeanFactory.containsBean("viewFactoryCreator")) {
return (ViewFactoryCreator) localFlowBeanFactory.getBean("viewFactoryCreator", ViewFactoryCreator.class);
} else {
return parent.getViewFactoryCreator();
}
}
public ConversionService getConversionService() {
if (localFlowBeanFactory.containsBean("conversionService")) {
return (ConversionService) localFlowBeanFactory.getBean("conversionService", ConversionService.class);
} else {
return parent.getConversionService();
}
}
public ExpressionParser getExpressionParser() {
if (localFlowBeanFactory.containsBean("expressionParser")) {
return (ExpressionParser) localFlowBeanFactory.getBean("expressionParser", ExpressionParser.class);
} else {
return parent.getExpressionParser();
}
}
public ResourceLoader getResourceLoader() {
return (ResourceLoader) getBeanFactory();
}
public BeanFactory getBeanFactory() {
return localFlowBeanFactory;
}
}

Some files were not shown because too many files have changed in this diff Show More