From 1a6cf956eede9bd2abafd06e8322b568de957c02 Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Fri, 21 Mar 2008 01:08:39 +0000 Subject: [PATCH] SWF-94 Add support for Flow Inheritance --- .../config/FlowRegistryFactoryBean.java | 25 +- .../registry/FlowDefinitionRegistryImpl.java | 4 + .../engine/builder/FlowModelFlowBuilder.java | 877 ++++++ .../{xml => }/LocalFlowBuilderContext.java | 5 +- .../RefreshableFlowDefinitionHolder.java | 3 + .../engine/builder/xml/XmlFlowBuilder.java | 1016 ------- .../webflow/engine/builder/xml/package.html | 7 - .../engine/model/AbstractActionModel.java | 26 + .../engine/model/AbstractMappingModel.java | 103 + .../webflow/engine/model/AbstractModel.java | 120 + .../engine/model/AbstractStateModel.java | 185 ++ .../AbstractTransitionableStateModel.java | 108 + .../engine/model/ActionStateModel.java | 174 ++ .../webflow/engine/model/AttributeModel.java | 160 ++ .../webflow/engine/model/BeanImportModel.java | 91 + .../engine/model/DecisionStateModel.java | 211 ++ .../webflow/engine/model/EndStateModel.java | 217 ++ .../webflow/engine/model/EvaluateModel.java | 168 ++ .../engine/model/ExceptionHandlerModel.java | 87 + .../webflow/engine/model/FlowModel.java | 705 +++++ .../webflow/engine/model/IfModel.java | 162 ++ .../webflow/engine/model/InputModel.java | 108 + .../webflow/engine/model/Model.java | 23 + .../webflow/engine/model/OutputModel.java | 108 + .../engine/model/PersistenceContextModel.java | 51 + .../webflow/engine/model/RenderModel.java | 93 + .../webflow/engine/model/SecuredModel.java | 139 + .../webflow/engine/model/SetModel.java | 161 ++ .../engine/model/SubflowStateModel.java | 275 ++ .../webflow/engine/model/TransitionModel.java | 311 +++ .../webflow/engine/model/VarModel.java | 161 ++ .../webflow/engine/model/ViewStateModel.java | 339 +++ .../model/builder/FlowModelBuilder.java | 70 + .../builder/FlowModelBuilderException.java | 46 + .../builder/xml/DefaultDocumentLoader.java | 2 +- .../builder/xml/DocumentLoader.java | 2 +- .../builder/xml/WebFlowEntityResolver.java | 2 +- .../builder/xml/XmlFlowModelBuilder.java | 589 ++++ .../builder/xml/spring-webflow-2.0.xsd | 2398 +++++++++-------- .../registry/DefaultFlowModelHolder.java | 168 ++ .../FlowModelConstructionException.java | 51 + .../model/registry/FlowModelHolder.java | 51 + .../model/registry/FlowModelLocator.java | 38 + .../model/registry/FlowModelRegistry.java | 45 + .../model/registry/FlowModelRegistryImpl.java | 102 + .../registry/NoSuchFlowModelException.java | 49 + ...bstractExternalizedFlowExecutionTests.java | 8 +- .../AbstractXmlFlowExecutionTests.java | 22 +- .../builder/FlowModelFlowBuilderTests.java | 299 ++ .../builder/xml/XmlFlowBuilderTests.java | 241 -- .../engine/model/AbstractModelTests.java | 94 + .../engine/model/ActionStateModelTests.java | 58 + .../engine/model/AttributeModelTests.java | 55 + .../engine/model/BeanImportModelTests.java | 49 + .../engine/model/DecisionStateModelTests.java | 58 + .../engine/model/EndStateModelTests.java | 57 + .../engine/model/EvaluateModelTests.java | 55 + .../model/ExceptionHandlerModelTests.java | 49 + .../webflow/engine/model/FlowModelTests.java | 188 ++ .../webflow/engine/model/IfModelTests.java | 55 + .../webflow/engine/model/InputModelTests.java | 57 + .../engine/model/OutputModelTests.java | 57 + .../model/PersistenceContextModelTests.java | 31 + .../engine/model/RenderModelTests.java | 49 + .../engine/model/SecuredModelTests.java | 55 + .../webflow/engine/model/SetModelTests.java | 55 + .../engine/model/SubflowStateModelTests.java | 55 + .../engine/model/TransitionModelTests.java | 55 + .../webflow/engine/model/VarModelTests.java | 55 + .../engine/model/ViewStateModelTests.java | 55 + .../builder/xml/XmlFlowModelBuilderTests.java | 275 ++ .../xml/flow-action-evaluate-action.xml | 0 .../builder/xml/flow-action-evaluate-bean.xml | 0 .../builder/xml/flow-custom-attribute.xml | 0 .../{ => model}/builder/xml/flow-endstate.xml | 0 .../builder/xml/flow-incomplete.xml | 0 .../builder/xml/flow-inheritance-child.xml | 8 + .../builder/xml/flow-inheritance-parent.xml} | 0 .../builder/xml/flow-inputoutput.xml | 0 .../builder/xml/flow-persistencecontext.xml | 0 .../builder/xml/flow-secured-state.xml | 0 .../builder/xml/flow-secured-transition.xml | 13 + .../{ => model}/builder/xml/flow-secured.xml | 0 .../builder/xml/flow-startstate-attribute.xml | 0 .../builder/xml/flow-startstate-default.xml | 0 .../{ => model}/builder/xml/flow-var.xml | 0 .../xml/flow-viewstate-externalredirect.xml | 0 .../xml/flow-viewstate-flowredirect.xml | 0 .../builder/xml/flow-viewstate-popup.xml | 0 .../builder/xml/flow-viewstate-redirect.xml | 0 .../builder/xml/flow-viewstate-var.xml | 0 .../builder/xml/flow-viewstate-view.xml | 0 .../registry/DefaultFlowModelHolderTests.java | 71 + .../registry/FlowModelRegistryImplTests.java | 58 + .../webflow/engine/model/registry/file.txt | 1 + 95 files changed, 9617 insertions(+), 2457 deletions(-) create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/builder/FlowModelFlowBuilder.java rename spring-webflow/src/main/java/org/springframework/webflow/engine/builder/{xml => }/LocalFlowBuilderContext.java (90%) delete mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilder.java delete mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/package.html create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/AbstractActionModel.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/AbstractMappingModel.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/AbstractModel.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/AbstractStateModel.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/AbstractTransitionableStateModel.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/ActionStateModel.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/AttributeModel.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/BeanImportModel.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/DecisionStateModel.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/EndStateModel.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/EvaluateModel.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/ExceptionHandlerModel.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/FlowModel.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/IfModel.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/InputModel.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/Model.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/OutputModel.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/PersistenceContextModel.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/RenderModel.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/SecuredModel.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/SetModel.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/SubflowStateModel.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/TransitionModel.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/VarModel.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/ViewStateModel.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/FlowModelBuilder.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/FlowModelBuilderException.java rename spring-webflow/src/main/java/org/springframework/webflow/engine/{ => model}/builder/xml/DefaultDocumentLoader.java (95%) rename spring-webflow/src/main/java/org/springframework/webflow/engine/{ => model}/builder/xml/DocumentLoader.java (93%) rename spring-webflow/src/main/java/org/springframework/webflow/engine/{ => model}/builder/xml/WebFlowEntityResolver.java (94%) create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/XmlFlowModelBuilder.java rename spring-webflow/src/main/java/org/springframework/webflow/engine/{ => model}/builder/xml/spring-webflow-2.0.xsd (52%) create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/DefaultFlowModelHolder.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/FlowModelConstructionException.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/FlowModelHolder.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/FlowModelLocator.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/FlowModelRegistry.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/FlowModelRegistryImpl.java create mode 100644 spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/NoSuchFlowModelException.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/builder/FlowModelFlowBuilderTests.java delete mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilderTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/AbstractModelTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/ActionStateModelTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/AttributeModelTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/BeanImportModelTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/DecisionStateModelTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/EndStateModelTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/EvaluateModelTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/ExceptionHandlerModelTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/FlowModelTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/IfModelTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/InputModelTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/OutputModelTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/PersistenceContextModelTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/RenderModelTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/SecuredModelTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/SetModelTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/SubflowStateModelTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/TransitionModelTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/VarModelTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/ViewStateModelTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/XmlFlowModelBuilderTests.java rename spring-webflow/src/test/java/org/springframework/webflow/engine/{ => model}/builder/xml/flow-action-evaluate-action.xml (100%) rename spring-webflow/src/test/java/org/springframework/webflow/engine/{ => model}/builder/xml/flow-action-evaluate-bean.xml (100%) rename spring-webflow/src/test/java/org/springframework/webflow/engine/{ => model}/builder/xml/flow-custom-attribute.xml (100%) rename spring-webflow/src/test/java/org/springframework/webflow/engine/{ => model}/builder/xml/flow-endstate.xml (100%) rename spring-webflow/src/test/java/org/springframework/webflow/engine/{ => model}/builder/xml/flow-incomplete.xml (100%) create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-inheritance-child.xml rename spring-webflow/src/test/java/org/springframework/webflow/engine/{builder/xml/flow-secured-transition.xml => model/builder/xml/flow-inheritance-parent.xml} (100%) rename spring-webflow/src/test/java/org/springframework/webflow/engine/{ => model}/builder/xml/flow-inputoutput.xml (100%) rename spring-webflow/src/test/java/org/springframework/webflow/engine/{ => model}/builder/xml/flow-persistencecontext.xml (100%) rename spring-webflow/src/test/java/org/springframework/webflow/engine/{ => model}/builder/xml/flow-secured-state.xml (100%) create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-secured-transition.xml rename spring-webflow/src/test/java/org/springframework/webflow/engine/{ => model}/builder/xml/flow-secured.xml (100%) rename spring-webflow/src/test/java/org/springframework/webflow/engine/{ => model}/builder/xml/flow-startstate-attribute.xml (100%) rename spring-webflow/src/test/java/org/springframework/webflow/engine/{ => model}/builder/xml/flow-startstate-default.xml (100%) rename spring-webflow/src/test/java/org/springframework/webflow/engine/{ => model}/builder/xml/flow-var.xml (100%) rename spring-webflow/src/test/java/org/springframework/webflow/engine/{ => model}/builder/xml/flow-viewstate-externalredirect.xml (100%) rename spring-webflow/src/test/java/org/springframework/webflow/engine/{ => model}/builder/xml/flow-viewstate-flowredirect.xml (100%) rename spring-webflow/src/test/java/org/springframework/webflow/engine/{ => model}/builder/xml/flow-viewstate-popup.xml (100%) rename spring-webflow/src/test/java/org/springframework/webflow/engine/{ => model}/builder/xml/flow-viewstate-redirect.xml (100%) rename spring-webflow/src/test/java/org/springframework/webflow/engine/{ => model}/builder/xml/flow-viewstate-var.xml (100%) rename spring-webflow/src/test/java/org/springframework/webflow/engine/{ => model}/builder/xml/flow-viewstate-view.xml (100%) create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/registry/DefaultFlowModelHolderTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/registry/FlowModelRegistryImplTests.java create mode 100644 spring-webflow/src/test/java/org/springframework/webflow/engine/model/registry/file.txt diff --git a/spring-webflow/src/main/java/org/springframework/webflow/config/FlowRegistryFactoryBean.java b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowRegistryFactoryBean.java index dcfb1b9a..f2e37356 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/config/FlowRegistryFactoryBean.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/config/FlowRegistryFactoryBean.java @@ -19,10 +19,15 @@ import org.springframework.webflow.definition.registry.FlowDefinitionRegistryImp 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.FlowModelFlowBuilder; 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; +import org.springframework.webflow.engine.model.builder.FlowModelBuilder; +import org.springframework.webflow.engine.model.builder.xml.XmlFlowModelBuilder; +import org.springframework.webflow.engine.model.registry.DefaultFlowModelHolder; +import org.springframework.webflow.engine.model.registry.FlowModelHolder; +import org.springframework.webflow.engine.model.registry.FlowModelRegistryImpl; /** * A factory for a flow definition registry. Is a Spring FactoryBean, for provision by the flow definition registry bean @@ -39,6 +44,11 @@ class FlowRegistryFactoryBean implements FactoryBean, InitializingBean { */ private FlowDefinitionRegistryImpl flowRegistry; + /** + * The model registry produced by this factory bean. + */ + private FlowModelRegistryImpl flowModelRegistry; + /** * Flow definitions defined in external files that should be registered in the registry produced by this factory * bean. @@ -76,6 +86,7 @@ class FlowRegistryFactoryBean implements FactoryBean, InitializingBean { public void afterPropertiesSet() throws Exception { flowResourceFactory = new FlowDefinitionResourceFactory(flowBuilderServices.getResourceLoader()); flowRegistry = new FlowDefinitionRegistryImpl(); + flowModelRegistry = new FlowModelRegistryImpl(); registerFlowLocations(); registerFlowBuilders(); } @@ -137,8 +148,18 @@ class FlowRegistryFactoryBean implements FactoryBean, InitializingBean { } private FlowBuilder createFlowBuilder(FlowDefinitionResource resource) { + return new FlowModelFlowBuilder(createFlowModelHolder(resource), resource.getPath()); + } + + private FlowModelHolder createFlowModelHolder(FlowDefinitionResource resource) { + FlowModelHolder modelHolder = new DefaultFlowModelHolder(createFlowModelBuilder(resource), resource.getId()); + flowModelRegistry.registerFlowModel(modelHolder); + return modelHolder; + } + + private FlowModelBuilder createFlowModelBuilder(FlowDefinitionResource resource) { if (isXml(resource.getPath())) { - return new XmlFlowBuilder(resource.getPath()); + return new XmlFlowModelBuilder(resource.getPath(), flowModelRegistry); } else { throw new IllegalArgumentException(resource + " is not a supported resource type; supported types are [.xml]"); diff --git a/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowDefinitionRegistryImpl.java b/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowDefinitionRegistryImpl.java index e3323e8e..5da184bc 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowDefinitionRegistryImpl.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowDefinitionRegistryImpl.java @@ -23,11 +23,13 @@ import org.apache.commons.logging.LogFactory; import org.springframework.core.style.ToStringCreator; import org.springframework.util.Assert; import org.springframework.webflow.definition.FlowDefinition; +import org.springframework.webflow.engine.model.registry.FlowModelConstructionException; /** * A generic registry implementation for housing one or more flow definitions. * * @author Keith Donald + * @author Scott Andrews */ public class FlowDefinitionRegistryImpl implements FlowDefinitionRegistry { @@ -65,6 +67,8 @@ public class FlowDefinitionRegistryImpl implements FlowDefinitionRegistry { return parent.getFlowDefinition(id); } throw e; + } catch (FlowModelConstructionException e) { + throw new FlowDefinitionConstructionException(e.getMessage(), e); } } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/FlowModelFlowBuilder.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/FlowModelFlowBuilder.java new file mode 100644 index 00000000..5f47b254 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/FlowModelFlowBuilder.java @@ -0,0 +1,877 @@ +package org.springframework.webflow.engine.builder; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.binding.convert.ConversionException; +import org.springframework.binding.convert.ConversionExecutor; +import org.springframework.binding.convert.support.RuntimeBindingConversionExecutor; +import org.springframework.binding.expression.EvaluationException; +import org.springframework.binding.expression.Expression; +import org.springframework.binding.expression.ExpressionParser; +import org.springframework.binding.expression.ParserContext; +import org.springframework.binding.expression.support.ParserContextImpl; +import org.springframework.binding.mapping.Mapper; +import org.springframework.binding.mapping.impl.DefaultMapper; +import org.springframework.binding.mapping.impl.DefaultMapping; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigUtils; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.style.ToStringCreator; +import org.springframework.util.StringUtils; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.GenericWebApplicationContext; +import org.springframework.webflow.action.ActionResultExposer; +import org.springframework.webflow.action.EvaluateAction; +import org.springframework.webflow.action.ExternalRedirectAction; +import org.springframework.webflow.action.FlowDefinitionRedirectAction; +import org.springframework.webflow.action.RenderAction; +import org.springframework.webflow.action.SetAction; +import org.springframework.webflow.action.ViewFactoryActionAdapter; +import org.springframework.webflow.core.collection.AttributeMap; +import org.springframework.webflow.core.collection.LocalAttributeMap; +import org.springframework.webflow.core.collection.MutableAttributeMap; +import org.springframework.webflow.definition.registry.FlowDefinitionLocator; +import org.springframework.webflow.engine.Flow; +import org.springframework.webflow.engine.FlowExecutionExceptionHandler; +import org.springframework.webflow.engine.FlowVariable; +import org.springframework.webflow.engine.SubflowAttributeMapper; +import org.springframework.webflow.engine.TargetStateResolver; +import org.springframework.webflow.engine.Transition; +import org.springframework.webflow.engine.TransitionCriteria; +import org.springframework.webflow.engine.VariableValueFactory; +import org.springframework.webflow.engine.ViewVariable; +import org.springframework.webflow.engine.builder.support.AbstractFlowBuilder; +import org.springframework.webflow.engine.builder.support.ActionExecutingViewFactory; +import org.springframework.webflow.engine.model.AbstractActionModel; +import org.springframework.webflow.engine.model.AbstractMappingModel; +import org.springframework.webflow.engine.model.AbstractStateModel; +import org.springframework.webflow.engine.model.ActionStateModel; +import org.springframework.webflow.engine.model.AttributeModel; +import org.springframework.webflow.engine.model.BeanImportModel; +import org.springframework.webflow.engine.model.DecisionStateModel; +import org.springframework.webflow.engine.model.EndStateModel; +import org.springframework.webflow.engine.model.EvaluateModel; +import org.springframework.webflow.engine.model.ExceptionHandlerModel; +import org.springframework.webflow.engine.model.FlowModel; +import org.springframework.webflow.engine.model.IfModel; +import org.springframework.webflow.engine.model.InputModel; +import org.springframework.webflow.engine.model.OutputModel; +import org.springframework.webflow.engine.model.PersistenceContextModel; +import org.springframework.webflow.engine.model.RenderModel; +import org.springframework.webflow.engine.model.SecuredModel; +import org.springframework.webflow.engine.model.SetModel; +import org.springframework.webflow.engine.model.SubflowStateModel; +import org.springframework.webflow.engine.model.TransitionModel; +import org.springframework.webflow.engine.model.VarModel; +import org.springframework.webflow.engine.model.ViewStateModel; +import org.springframework.webflow.engine.model.registry.FlowModelHolder; +import org.springframework.webflow.engine.support.BeanFactoryVariableValueFactory; +import org.springframework.webflow.engine.support.DefaultTransitionCriteria; +import org.springframework.webflow.engine.support.GenericSubflowAttributeMapper; +import org.springframework.webflow.engine.support.TransitionCriteriaChain; +import org.springframework.webflow.engine.support.TransitionExecutingFlowExecutionExceptionHandler; +import org.springframework.webflow.execution.Action; +import org.springframework.webflow.execution.RequestContext; +import org.springframework.webflow.execution.ScopeType; +import org.springframework.webflow.execution.ViewFactory; +import org.springframework.webflow.security.SecurityRule; +import org.springframework.webflow.util.ResourceHolder; + +public class FlowModelFlowBuilder extends AbstractFlowBuilder implements ResourceHolder { + + private FlowModelHolder flowModelHolder; + private FlowModel flowModel; + private LocalFlowBuilderContext localFlowBuilderContext; + private Resource resource; + + public FlowModelFlowBuilder(FlowModelHolder flowModelHolder) { + this.flowModelHolder = flowModelHolder; + } + + public FlowModelFlowBuilder(FlowModelHolder flowModelHolder, Resource resource) { + this.flowModelHolder = flowModelHolder; + this.resource = resource; + } + + /** + * Initialize this builder. This could cause the builder to open a stream to an externalized resource representing + * the flow definition, for example. + * @throws FlowBuilderException an exception occurred building the flow + */ + public void doInit() throws FlowBuilderException { + flowModel = flowModelHolder.getFlowModel(); + initLocalFlowContext(); + } + + /** + * Builds any variables initialized by the flow when it starts. + * @throws FlowBuilderException an exception occurred building the flow + */ + public void buildVariables() throws FlowBuilderException { + if (flowModel.getVars() != null) { + for (Iterator varIt = flowModel.getVars().iterator(); varIt.hasNext();) { + getFlow().addVariable(convertFlowVariable((VarModel) varIt.next())); + } + } + } + + /** + * Builds the input mapper responsible for mapping flow input on start. + * @throws FlowBuilderException an exception occurred building the flow + */ + public void buildInputMapper() throws FlowBuilderException { + if (flowModel.getInputs() != null) { + getFlow().setInputMapper(convertFlowInputMapper(flowModel.getInputs())); + } + } + + /** + * Builds any start actions to execute when the flow starts. + * @throws FlowBuilderException an exception occurred building the flow + */ + public void buildStartActions() throws FlowBuilderException { + if (flowModel.getOnStartActions() != null) { + getFlow().getStartActionList().addAll(convertActions(flowModel.getOnStartActions())); + } + } + + /** + * Builds the states of the flow. + * @throws FlowBuilderException an exception occurred building the flow + */ + public void buildStates() throws FlowBuilderException { + if (flowModel.getStates() == null) { + throw new FlowBuilderException("At least one state is required to build a flow definition"); + } + for (Iterator stateIt = flowModel.getStates().iterator(); stateIt.hasNext();) { + AbstractStateModel state = (AbstractStateModel) stateIt.next(); + if (state instanceof ActionStateModel) { + convertActionState((ActionStateModel) state, getFlow()); + } else if (state instanceof ViewStateModel) { + convertViewState((ViewStateModel) state, getFlow()); + } else if (state instanceof DecisionStateModel) { + convertDecisionState((DecisionStateModel) state, getFlow()); + } else if (state instanceof SubflowStateModel) { + convertSubflowState((SubflowStateModel) state, getFlow()); + } else if (state instanceof EndStateModel) { + convertEndState((EndStateModel) state, getFlow()); + } + } + if (flowModel.getStartStateId() != null) { + getFlow().setStartState(flowModel.getStartStateId()); + } else { + // default to the identifier of the first state in the flow model + getFlow().setStartState(((AbstractStateModel) flowModel.getStates().get(0)).getId()); + } + } + + /** + * Builds any transitions shared by all states of the flow. + * @throws FlowBuilderException an exception occurred building the flow + */ + public void buildGlobalTransitions() throws FlowBuilderException { + if (flowModel.getGlobalTransitions() != null) { + getFlow().getGlobalTransitionSet().addAll(convertTransitions(flowModel.getGlobalTransitions())); + } + } + + /** + * Builds any end actions to execute when the flow ends. + * @throws FlowBuilderException an exception occurred building the flow + */ + public void buildEndActions() throws FlowBuilderException { + if (flowModel.getOnEndActions() != null) { + getFlow().getEndActionList().addAll(convertActions(flowModel.getOnEndActions())); + } + } + + /** + * Builds the output mapper responsible for mapping flow output on end. + * @throws FlowBuilderException an exception occurred building the flow + */ + public void buildOutputMapper() throws FlowBuilderException { + if (flowModel.getOutputs() != null) { + getFlow().setOutputMapper(convertFlowOutputMapper(flowModel.getOutputs())); + } + } + + /** + * Creates and adds all exception handlers to the flow built by this builder. + * @throws FlowBuilderException an exception occurred building this flow + */ + public void buildExceptionHandlers() throws FlowBuilderException { + getFlow().getExceptionHandlerSet().addAll( + convertExceptionHandlers(flowModel.getExceptionHandlers(), flowModel.getGlobalTransitions())); + } + + /** + * Shutdown the builder, releasing any resources it holds. A new flow construction process should start with another + * call to the {@link #init(FlowBuilderContext)} method. + * @throws FlowBuilderException an exception occurred building this flow + */ + public void doDispose() throws FlowBuilderException { + flowModel = null; + setLocalContext(null); + } + + private void initLocalFlowContext() { + List resources = new LinkedList(); + if (getFlowModel().getBeanImports() != null) { + if (getResource() == null) { + throw new FlowBuilderException("A resource must be defined in order to load bean-imports"); + } + for (Iterator beanImportIt = getFlowModel().getBeanImports().iterator(); beanImportIt.hasNext();) { + BeanImportModel beanImport = (BeanImportModel) beanImportIt.next(); + try { + resources.add(getResource().createRelative(beanImport.getResource())); + } catch (IOException e) { + throw new FlowBuilderException("Could not access flow-relative artifact resource '" + + beanImport.getResource() + "'", e); + } + } + } + setLocalContext(new LocalFlowBuilderContext(getContext(), createFlowApplicationContext((Resource[]) resources + .toArray(new Resource[resources.size()])))); + } + + protected Flow createFlow() { + Flow flow = convertFlow(flowModel); + flow.setBeanFactory(getLocalContext().getBeanFactory()); + flow.setResourceLoader(getLocalContext().getResourceLoader()); + return flow; + } + + private Flow convertFlow(FlowModel flow) { + String flowId = getLocalContext().getFlowId(); + AttributeMap externallyAssignedAttributes = getLocalContext().getFlowAttributes(); + MutableAttributeMap flowAttributes = convertMetaAttributes(flow.getAttributes()); + convertPersistenceContext(flow.getPersistenceContext(), flowAttributes); + convertSecured(flow.getSecured(), flowAttributes); + return this.getLocalContext().getFlowArtifactFactory().createFlow(flowId, + flowAttributes.union(externallyAssignedAttributes)); + } + + protected GenericApplicationContext createFlowApplicationContext(Resource[] resources) { + // see if this factory has a parent + BeanFactory parent = getContext().getBeanFactory(); + // determine the context implementation based on the current environment + GenericApplicationContext flowContext; + if (parent instanceof WebApplicationContext) { + GenericWebApplicationContext webContext = new GenericWebApplicationContext(); + webContext.setServletContext(((WebApplicationContext) parent).getServletContext()); + flowContext = webContext; + } else { + flowContext = new GenericApplicationContext(); + } + // set the parent if necessary + if (parent instanceof ApplicationContext) { + flowContext.setParent((ApplicationContext) parent); + } else { + if (parent != null) { + flowContext.getBeanFactory().setParentBeanFactory(parent); + } + } + flowContext.setResourceLoader(new FlowRelativeResourceLoader(resource)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(flowContext); + new XmlBeanDefinitionReader(flowContext).loadBeanDefinitions(resources); + registerFlowBeans(flowContext.getDefaultListableBeanFactory()); + flowContext.refresh(); + return flowContext; + } + + /** + * Register beans in the bean factory local to the flow definition being built. + *

+ * Subclasses may override this method to customize the population of the bean factory local to the flow definition + * being built; for example, to register mock implementations of services in a test environment. + * @param beanFactory the bean factory; register local beans with it using + * {@link ConfigurableBeanFactory#registerSingleton(String, Object)} + */ + protected void registerFlowBeans(ConfigurableBeanFactory beanFactory) { + } + + private FlowVariable convertFlowVariable(VarModel var) { + Class clazz = (Class) fromStringTo(Class.class).execute(var.getClassName()); + VariableValueFactory valueFactory = new BeanFactoryVariableValueFactory(clazz, + (AutowireCapableBeanFactory) getFlow().getBeanFactory()); + ScopeType scope = convertScopeType(var.getScope(), ScopeType.FLOW); + if (!(scope == ScopeType.FLOW || scope == ScopeType.CONVERSATION)) { + throw new IllegalArgumentException("Only " + ScopeType.FLOW + " or " + ScopeType.CONVERSATION + + " scope is allowed for flow variables"); + } + return new FlowVariable(var.getName(), valueFactory, scope == ScopeType.FLOW ? true : false); + } + + private Mapper convertFlowInputMapper(List inputs) { + DefaultMapper inputMapper = new DefaultMapper(); + if (inputs != null) { + for (Iterator inputIt = inputs.iterator(); inputIt.hasNext();) { + inputMapper.addMapping(convertFlowInputMapping((InputModel) inputIt.next())); + } + } + return inputMapper; + } + + private DefaultMapping convertFlowInputMapping(InputModel input) { + ExpressionParser parser = getLocalContext().getExpressionParser(); + String name = input.getName(); + String value = null; + if (StringUtils.hasText(input.getValue())) { + value = input.getValue(); + } else { + value = name; + } + Expression source = parser.parseExpression(name, new ParserContextImpl().eval(MutableAttributeMap.class)); + Expression target = parser.parseExpression(value, new ParserContextImpl().eval(RequestContext.class)); + DefaultMapping mapping = new DefaultMapping(source, target); + convertMappingConversionExecutor(input, mapping); + convertMappingRequired(input, mapping); + return mapping; + } + + private Mapper convertSubflowInputMapper(List inputs) { + DefaultMapper inputMapper = new DefaultMapper(); + if (inputs != null) { + for (Iterator inputIt = inputs.iterator(); inputIt.hasNext();) { + inputMapper.addMapping(convertSubflowInputMapping((InputModel) inputIt.next())); + } + } + return inputMapper; + } + + private DefaultMapping convertSubflowInputMapping(InputModel input) { + ExpressionParser parser = getLocalContext().getExpressionParser(); + String name = input.getName(); + String value = null; + if (StringUtils.hasText(input.getValue())) { + value = input.getValue(); + } else { + value = name; + } + Expression source = parser.parseExpression(value, new ParserContextImpl().eval(RequestContext.class)); + Expression target = parser.parseExpression(name, new ParserContextImpl().eval(MutableAttributeMap.class)); + DefaultMapping mapping = new DefaultMapping(source, target); + convertMappingConversionExecutor(input, mapping); + convertMappingRequired(input, mapping); + return mapping; + } + + private Mapper convertFlowOutputMapper(List outputs) { + DefaultMapper outputMapper = new DefaultMapper(); + if (outputs != null) { + for (Iterator outputIt = outputs.iterator(); outputIt.hasNext();) { + outputMapper.addMapping(convertFlowOutputMapping((OutputModel) outputIt.next())); + } + } + return outputMapper; + } + + private DefaultMapping convertFlowOutputMapping(OutputModel output) { + ExpressionParser parser = getLocalContext().getExpressionParser(); + String name = output.getName(); + String value = null; + if (StringUtils.hasText(output.getValue())) { + value = output.getValue(); + } else { + value = name; + } + Expression source = parser.parseExpression(value, new ParserContextImpl().eval(RequestContext.class)); + Expression target = parser.parseExpression(name, new ParserContextImpl().eval(MutableAttributeMap.class)); + DefaultMapping mapping = new DefaultMapping(source, target); + convertMappingConversionExecutor(output, mapping); + convertMappingRequired(output, mapping); + return mapping; + } + + private Mapper convertSubflowOutputMapper(List outputs) { + DefaultMapper outputMapper = new DefaultMapper(); + if (outputs != null) { + for (Iterator outputIt = outputs.iterator(); outputIt.hasNext();) { + outputMapper.addMapping(convertSubflowOutputMapping((OutputModel) outputIt.next())); + } + } + return outputMapper; + } + + private DefaultMapping convertSubflowOutputMapping(OutputModel output) { + ExpressionParser parser = getLocalContext().getExpressionParser(); + String name = output.getName(); + String value = null; + if (StringUtils.hasText(output.getValue())) { + value = output.getValue(); + } else { + value = name; + } + Expression source = parser.parseExpression(name, new ParserContextImpl().eval(MutableAttributeMap.class)); + Expression target = parser.parseExpression(value, new ParserContextImpl().eval(RequestContext.class)); + DefaultMapping mapping = new DefaultMapping(source, target); + convertMappingConversionExecutor(output, mapping); + convertMappingRequired(output, mapping); + return mapping; + } + + private void convertMappingConversionExecutor(AbstractMappingModel model, DefaultMapping mapping) { + if (StringUtils.hasText(model.getType())) { + Class type = (Class) fromStringTo(Class.class).execute(model.getType()); + ConversionExecutor typeConverter = new RuntimeBindingConversionExecutor(type, getLocalContext() + .getConversionService()); + mapping.setTypeConverter(typeConverter); + } + } + + private void convertMappingRequired(AbstractMappingModel model, DefaultMapping mapping) { + if (StringUtils.hasText(model.getRequired())) { + boolean required = ((Boolean) fromStringTo(Boolean.class).execute(model.getRequired())).booleanValue(); + mapping.setRequired(required); + } + } + + private void convertActionState(ActionStateModel state, Flow flow) { + MutableAttributeMap attributes = convertMetaAttributes(state.getAttributes()); + convertSecured(state.getSecured(), attributes); + getLocalContext().getFlowArtifactFactory().createActionState(state.getId(), flow, + convertActions(state.getOnEntryActions()), convertActions(state.getActions()), + convertTransitions(state.getTransitions()), + convertExceptionHandlers(state.getExceptionHandlers(), state.getTransitions()), + convertActions(state.getOnExitActions()), attributes); + } + + private void convertViewState(ViewStateModel state, Flow flow) { + ViewFactory viewFactory = convertViewFactory(state.getView(), state.getId(), false); + Boolean redirect = null; + if (StringUtils.hasText(state.getRedirect())) { + redirect = (Boolean) fromStringTo(Boolean.class).execute(state.getRedirect()); + } + boolean popup = false; + if (StringUtils.hasText(state.getPopup())) { + popup = ((Boolean) fromStringTo(Boolean.class).execute(state.getPopup())).booleanValue(); + } + MutableAttributeMap attributes = convertMetaAttributes(state.getAttributes()); + convertSecured(state.getSecured(), attributes); + getLocalContext().getFlowArtifactFactory().createViewState(state.getId(), flow, + convertViewVariables(state.getVars()), convertActions(state.getOnEntryActions()), viewFactory, + redirect, popup, convertActions(state.getOnRenderActions()), + convertTransitions(state.getTransitions()), + convertExceptionHandlers(state.getExceptionHandlers(), state.getTransitions()), + convertActions(state.getOnExitActions()), attributes); + } + + private void convertDecisionState(DecisionStateModel state, Flow flow) { + MutableAttributeMap attributes = convertMetaAttributes(state.getAttributes()); + convertSecured(state.getSecured(), attributes); + getLocalContext().getFlowArtifactFactory().createDecisionState(state.getId(), flow, + convertActions(state.getOnEntryActions()), convertIfs(state.getIfs()), + convertExceptionHandlers(state.getExceptionHandlers(), null), convertActions(state.getOnExitActions()), + attributes); + } + + private void convertSubflowState(SubflowStateModel state, Flow flow) { + MutableAttributeMap attributes = convertMetaAttributes(state.getAttributes()); + convertSecured(state.getSecured(), attributes); + getLocalContext().getFlowArtifactFactory().createSubflowState(state.getId(), flow, + convertActions(state.getOnEntryActions()), convertSubflowExpression(state.getSubflow()), + convertSubflowAttributeMapper(state), convertTransitions(state.getTransitions()), + convertExceptionHandlers(state.getExceptionHandlers(), state.getTransitions()), + convertActions(state.getOnExitActions()), attributes); + } + + private Expression convertSubflowExpression(String subflow) { + Expression subflowId = getLocalContext().getExpressionParser().parseExpression(subflow, + new ParserContextImpl().template().eval(RequestContext.class).expect(String.class)); + return new SubflowExpression(subflowId, getLocalContext().getFlowDefinitionLocator()); + } + + private SubflowAttributeMapper convertSubflowAttributeMapper(SubflowStateModel state) { + if (StringUtils.hasText(state.getSubflowAttributeMapper())) { + String attributeMapperBeanId = state.getSubflowAttributeMapper(); + return (SubflowAttributeMapper) getLocalContext().getBeanFactory().getBean(attributeMapperBeanId, + SubflowAttributeMapper.class); + } else { + Mapper inputMapper = convertSubflowInputMapper(state.getInputs()); + Mapper outputMapper = convertSubflowOutputMapper(state.getOutputs()); + return new GenericSubflowAttributeMapper(inputMapper, outputMapper); + } + } + + private void convertEndState(EndStateModel state, Flow flow) { + MutableAttributeMap attributes = convertMetaAttributes(state.getAttributes()); + if (StringUtils.hasText(state.getCommit())) { + attributes.put("commit", fromStringTo(Boolean.class).execute(state.getCommit())); + } + convertSecured(state.getSecured(), attributes); + getLocalContext().getFlowArtifactFactory().createEndState(state.getId(), flow, + convertActions(state.getOnEntryActions()), + new ViewFactoryActionAdapter(convertViewFactory(state.getView(), state.getId(), true)), + convertFlowOutputMapper(state.getOutputs()), + convertExceptionHandlers(state.getExceptionHandlers(), null), attributes); + } + + private ViewFactory convertViewFactory(String view, String stateId, boolean endState) { + if (!StringUtils.hasText(view)) { + if (endState) { + return null; + } else { + view = getLocalContext().getViewFactoryCreator().getViewIdByConvention(stateId); + Expression viewId = getLocalContext().getExpressionParser().parseExpression(view, + new ParserContextImpl().template().eval(RequestContext.class).expect(String.class)); + return createViewFactory(viewId); + } + } else if (view.startsWith("externalRedirect:")) { + String encodedUrl = view.substring("externalRedirect:".length()); + Expression externalUrl = getLocalContext().getExpressionParser().parseExpression(encodedUrl, + new ParserContextImpl().template().eval(RequestContext.class).expect(String.class)); + return new ActionExecutingViewFactory(new ExternalRedirectAction(externalUrl)); + } else if (view.startsWith("flowRedirect:")) { + String flowRedirect = view.substring("flowRedirect:".length()); + Expression expression = getLocalContext().getExpressionParser().parseExpression(flowRedirect, + new ParserContextImpl().template().eval(RequestContext.class).expect(String.class)); + return new ActionExecutingViewFactory(new FlowDefinitionRedirectAction(expression)); + } else { + Expression viewId = getLocalContext().getExpressionParser().parseExpression(view, + new ParserContextImpl().template().eval(RequestContext.class).expect(String.class)); + return createViewFactory(viewId); + } + } + + private ViewFactory createViewFactory(Expression viewId) { + return getLocalContext().getViewFactoryCreator().createViewFactory(viewId, + getLocalContext().getExpressionParser(), getLocalContext().getFormatterRegistry(), + getLocalContext().getResourceLoader()); + } + + private ViewVariable[] convertViewVariables(List vars) { + List variables = new LinkedList(); + if (vars != null) { + for (Iterator varIt = vars.iterator(); varIt.hasNext();) { + variables.add(convertViewVariable((VarModel) varIt.next())); + } + } + return (ViewVariable[]) variables.toArray(new ViewVariable[variables.size()]); + } + + private ViewVariable convertViewVariable(VarModel var) { + Class clazz = (Class) fromStringTo(Class.class).execute(var.getClassName()); + VariableValueFactory valueFactory = new BeanFactoryVariableValueFactory(clazz, + (AutowireCapableBeanFactory) getFlow().getBeanFactory()); + return new ViewVariable(var.getName(), valueFactory); + } + + private Transition[] convertIfs(List ifs) { + List transitions = new LinkedList(); + if (ifs != null) { + for (Iterator ifIt = ifs.iterator(); ifIt.hasNext();) { + transitions.addAll(Arrays.asList(convertIf((IfModel) ifIt.next()))); + } + } + return (Transition[]) transitions.toArray(new Transition[transitions.size()]); + } + + private Transition[] convertIf(IfModel conditional) { + Transition thenTransition = convertThen(conditional); + if (StringUtils.hasText(conditional.getElse())) { + Transition elseTransition = convertElse(conditional); + return new Transition[] { thenTransition, elseTransition }; + } else { + return new Transition[] { thenTransition }; + } + } + + private Transition convertThen(IfModel conditional) { + Expression expression = getLocalContext().getExpressionParser().parseExpression(conditional.getTest(), + new ParserContextImpl().eval(RequestContext.class).expect(Boolean.class)); + TransitionCriteria matchingCriteria = new DefaultTransitionCriteria(expression); + TargetStateResolver targetStateResolver = (TargetStateResolver) fromStringTo(TargetStateResolver.class) + .execute(conditional.getThen()); + return getLocalContext().getFlowArtifactFactory().createTransition(targetStateResolver, matchingCriteria, null, + null); + } + + private Transition convertElse(IfModel conditional) { + TargetStateResolver targetStateResolver = (TargetStateResolver) fromStringTo(TargetStateResolver.class) + .execute(conditional.getElse()); + return getLocalContext().getFlowArtifactFactory().createTransition(targetStateResolver, null, null, null); + } + + private FlowExecutionExceptionHandler[] convertExceptionHandlers(List modelExceptionHandlers, List modelTransitions) { + FlowExecutionExceptionHandler[] transitionExecutingHandlers = convertTransitionExecutingExceptionHandlers(modelTransitions); + FlowExecutionExceptionHandler[] customHandlers = convertCustomExceptionHandlers(modelExceptionHandlers); + FlowExecutionExceptionHandler[] exceptionHandlers = new FlowExecutionExceptionHandler[transitionExecutingHandlers.length + + customHandlers.length]; + System.arraycopy(transitionExecutingHandlers, 0, exceptionHandlers, 0, transitionExecutingHandlers.length); + System.arraycopy(customHandlers, 0, exceptionHandlers, transitionExecutingHandlers.length, + customHandlers.length); + return exceptionHandlers; + } + + private FlowExecutionExceptionHandler[] convertTransitionExecutingExceptionHandlers(List transitions) { + List exceptionHandlers = new LinkedList(); + if (transitions != null) { + for (Iterator transitionIt = transitions.iterator(); transitionIt.hasNext();) { + TransitionModel transition = (TransitionModel) transitionIt.next(); + if (StringUtils.hasText(transition.getOnException())) { + if (transition.getSecured() != null) { + throw new FlowBuilderException("Exception based transitions cannot be secured"); + } + exceptionHandlers.add(convertTransitionExecutingExceptionHandler(transition)); + } + } + } + return (FlowExecutionExceptionHandler[]) exceptionHandlers + .toArray(new FlowExecutionExceptionHandler[exceptionHandlers.size()]); + } + + private FlowExecutionExceptionHandler convertTransitionExecutingExceptionHandler(TransitionModel transition) { + TransitionExecutingFlowExecutionExceptionHandler handler = new TransitionExecutingFlowExecutionExceptionHandler(); + Class exceptionClass = (Class) fromStringTo(Class.class).execute(transition.getOnException()); + TargetStateResolver targetStateResolver = (TargetStateResolver) fromStringTo(TargetStateResolver.class) + .execute(transition.getTo()); + handler.add(exceptionClass, targetStateResolver); + handler.getActionList().addAll(convertActions(transition.getActions())); + return handler; + } + + private FlowExecutionExceptionHandler[] convertCustomExceptionHandlers(List modelExceptionHandlers) { + List exceptionHandlers = new LinkedList(); + if (modelExceptionHandlers != null) { + for (Iterator exceptionHandlerIt = modelExceptionHandlers.iterator(); exceptionHandlerIt.hasNext();) { + exceptionHandlers.add(convertCustomExceptionHandler((ExceptionHandlerModel) exceptionHandlerIt.next())); + } + } + return (FlowExecutionExceptionHandler[]) exceptionHandlers + .toArray(new FlowExecutionExceptionHandler[exceptionHandlers.size()]); + } + + private FlowExecutionExceptionHandler convertCustomExceptionHandler(ExceptionHandlerModel exceptionHandler) { + return (FlowExecutionExceptionHandler) getLocalContext().getBeanFactory().getBean( + exceptionHandler.getBeanName(), FlowExecutionExceptionHandler.class); + } + + private Transition[] convertTransitions(List modelTransactions) { + List transitions = new LinkedList(); + if (modelTransactions != null) { + for (Iterator modelTransactionIt = modelTransactions.iterator(); modelTransactionIt.hasNext();) { + TransitionModel transition = (TransitionModel) modelTransactionIt.next(); + if (!StringUtils.hasText(transition.getOnException())) { + transitions.add(convertTransition(transition)); + } + } + } + return (Transition[]) transitions.toArray(new Transition[transitions.size()]); + } + + private Transition convertTransition(TransitionModel transition) { + TransitionCriteria matchingCriteria = (TransitionCriteria) fromStringTo(TransitionCriteria.class).execute( + transition.getOn()); + TargetStateResolver targetStateResolver = (TargetStateResolver) fromStringTo(TargetStateResolver.class) + .execute(transition.getTo()); + TransitionCriteria executionCriteria = TransitionCriteriaChain.criteriaChainFor(convertActions(transition + .getActions())); + MutableAttributeMap attributes = convertMetaAttributes(transition.getAttributes()); + convertSecured(transition.getSecured(), attributes); + return getLocalContext().getFlowArtifactFactory().createTransition(targetStateResolver, matchingCriteria, + executionCriteria, attributes); + } + + private Action[] convertActions(List modelActions) { + List actions = new LinkedList(); + if (modelActions != null) { + for (Iterator modelActionIt = modelActions.iterator(); modelActionIt.hasNext();) { + AbstractActionModel action = (AbstractActionModel) modelActionIt.next(); + if (action instanceof EvaluateModel) { + actions.add(convertEvaluateAction((EvaluateModel) action)); + } else if (action instanceof RenderModel) { + actions.add(convertRenderAction((RenderModel) action)); + } else if (action instanceof SetModel) { + actions.add(convertSetAction((SetModel) action)); + } + } + } + return (Action[]) actions.toArray(new Action[actions.size()]); + } + + private Action convertEvaluateAction(EvaluateModel evaluate) { + String expressionString = evaluate.getExpression(); + Expression expression = getLocalContext().getExpressionParser().parseExpression(expressionString, + new ParserContextImpl().eval(RequestContext.class)); + return new EvaluateAction(expression, convertEvaluationActionResultExposer(evaluate)); + } + + private ActionResultExposer convertEvaluationActionResultExposer(EvaluateModel evaluate) { + if (StringUtils.hasText(evaluate.getResult())) { + Expression resultExpression = getLocalContext().getExpressionParser().parseExpression(evaluate.getResult(), + new ParserContextImpl().eval(RequestContext.class)); + Class expectedResultType = null; + if (StringUtils.hasText(evaluate.getResultType())) { + expectedResultType = (Class) fromStringTo(Class.class).execute(evaluate.getResultType()); + } + return new ActionResultExposer(resultExpression, expectedResultType, getLocalContext() + .getConversionService()); + } else { + return null; + } + } + + private Action convertRenderAction(RenderModel render) { + String[] fragmentExpressionStrings = StringUtils.commaDelimitedListToStringArray(render.getFragments()); + fragmentExpressionStrings = StringUtils.trimArrayElements(fragmentExpressionStrings); + ParserContext context = new ParserContextImpl().template().eval(RequestContext.class).expect(String.class); + Expression[] fragments = new Expression[fragmentExpressionStrings.length]; + for (int i = 0; i < fragmentExpressionStrings.length; i++) { + String fragment = fragmentExpressionStrings[i]; + fragments[i] = getLocalContext().getExpressionParser().parseExpression(fragment, context); + } + return new RenderAction(fragments); + } + + private Action convertSetAction(SetModel set) { + Expression nameExpression = getLocalContext().getExpressionParser().parseExpression(set.getName(), + new ParserContextImpl().eval(RequestContext.class)); + Expression valueExpression = getLocalContext().getExpressionParser().parseExpression(set.getValue(), + new ParserContextImpl().eval(RequestContext.class)); + Class expectedType = null; + if (StringUtils.hasText(set.getType())) { + expectedType = (Class) fromStringTo(Class.class).execute(set.getType()); + } + return new SetAction(nameExpression, valueExpression, expectedType, getLocalContext().getConversionService()); + } + + private MutableAttributeMap convertMetaAttributes(List modelAttributes) { + LocalAttributeMap attributes = new LocalAttributeMap(); + if (modelAttributes != null) { + for (Iterator modelAttributeIt = modelAttributes.iterator(); modelAttributeIt.hasNext();) { + convertMetaAttribute((AttributeModel) modelAttributeIt.next(), attributes); + } + } + return attributes; + } + + private void convertMetaAttribute(AttributeModel attribute, MutableAttributeMap attributes) { + String name = attribute.getName(); + String value = attribute.getValue(); + attributes.put(name, convertAttributeValueIfNecessary(attribute, value)); + } + + private Object convertAttributeValueIfNecessary(AttributeModel attribute, String stringValue) { + if (StringUtils.hasText(attribute.getType())) { + Class targetClass = (Class) fromStringTo(Class.class).execute(attribute.getType()); + return fromStringTo(targetClass).execute(stringValue); + } else { + return stringValue; + } + } + + private void convertPersistenceContext(PersistenceContextModel persistenceContext, MutableAttributeMap attributes) { + if (persistenceContext != null) { + attributes.put("persistenceContext", Boolean.TRUE); + } + } + + private void convertSecured(SecuredModel secured, MutableAttributeMap attributes) { + if (secured != null) { + SecurityRule rule = new SecurityRule(); + rule.setAttributes(SecurityRule.convertAttributesFromCommaSeparatedString(secured.getAttributes())); + String comparisonType = secured.getMatch(); + if ("any".equals(comparisonType)) { + rule.setComparisonType(SecurityRule.COMPARISON_ANY); + } else if ("all".equals(comparisonType)) { + rule.setComparisonType(SecurityRule.COMPARISON_ALL); + } else { + // default to any + rule.setComparisonType(SecurityRule.COMPARISON_ANY); + } + attributes.put(SecurityRule.SECURITY_ATTRIBUTE_NAME, rule); + } + } + + private ScopeType convertScopeType(String scope, ScopeType defaultScope) { + if (StringUtils.hasText(scope)) { + return (ScopeType) fromStringTo(ScopeType.class).execute(scope); + } else { + return defaultScope; + } + } + + private ConversionExecutor fromStringTo(Class targetType) throws ConversionException { + return getLocalContext().getConversionService().getConversionExecutor(String.class, targetType); + } + + protected FlowModel getFlowModel() { + return flowModel; + } + + protected LocalFlowBuilderContext getLocalContext() { + return localFlowBuilderContext; + } + + public Resource getResource() { + return resource; + } + + protected void setLocalContext(LocalFlowBuilderContext localFlowBuilderContext) { + this.localFlowBuilderContext = localFlowBuilderContext; + } + + private static class FlowRelativeResourceLoader implements ResourceLoader { + private Resource resource; + + public FlowRelativeResourceLoader(Resource resource) { + this.resource = resource; + } + + public ClassLoader getClassLoader() { + return resource.getClass().getClassLoader(); + } + + public Resource getResource(String location) { + try { + return resource.createRelative(location); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private static class SubflowExpression implements Expression { + + private Expression subflowId; + + private FlowDefinitionLocator flowDefinitionLocator; + + public SubflowExpression(Expression subflowId, FlowDefinitionLocator flowDefinitionLocator) { + this.subflowId = subflowId; + this.flowDefinitionLocator = flowDefinitionLocator; + } + + public Object getValue(Object context) throws EvaluationException { + String subflowId = (String) this.subflowId.getValue(context); + return flowDefinitionLocator.getFlowDefinition(subflowId); + } + + public void setValue(Object context, Object value) throws EvaluationException { + throw new UnsupportedOperationException("Cannot set a subflow expression"); + } + + public Class getValueType(Object context) { + return null; + } + + public String getExpressionString() { + return null; + } + } + + public String toString() { + return new ToStringCreator(this).toString(); + } + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/LocalFlowBuilderContext.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/LocalFlowBuilderContext.java similarity index 90% rename from spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/LocalFlowBuilderContext.java rename to spring-webflow/src/main/java/org/springframework/webflow/engine/builder/LocalFlowBuilderContext.java index 43f7ce7b..3d836c0c 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/LocalFlowBuilderContext.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/LocalFlowBuilderContext.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.webflow.engine.builder.xml; +package org.springframework.webflow.engine.builder; import org.springframework.beans.factory.BeanFactory; import org.springframework.binding.convert.ConversionService; @@ -23,9 +23,6 @@ import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.io.ResourceLoader; 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; /** * A builder context that delegates to a flow-local bean factory for builder services. Such builder services override diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/RefreshableFlowDefinitionHolder.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/RefreshableFlowDefinitionHolder.java index 77e85024..ba6cbe40 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/RefreshableFlowDefinitionHolder.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/RefreshableFlowDefinitionHolder.java @@ -138,6 +138,9 @@ public class RefreshableFlowDefinitionHolder implements FlowDefinitionHolder { private void refreshIfChanged() { long calculatedLastModified = calculateLastModified(); if (calculatedLastModified > lastModified) { + if (logger.isDebugEnabled()) { + logger.debug("Refreshing flow definition [" + flowDefinition.getId() + "]"); + } assembleFlow(); lastModified = calculatedLastModified; } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilder.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilder.java deleted file mode 100644 index 628ba7dc..00000000 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilder.java +++ /dev/null @@ -1,1016 +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.xml; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; - -import javax.xml.parsers.ParserConfigurationException; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.config.AutowireCapableBeanFactory; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; -import org.springframework.binding.convert.ConversionException; -import org.springframework.binding.convert.ConversionExecutor; -import org.springframework.binding.convert.ConversionService; -import org.springframework.binding.convert.support.RuntimeBindingConversionExecutor; -import org.springframework.binding.expression.EvaluationException; -import org.springframework.binding.expression.Expression; -import org.springframework.binding.expression.ExpressionParser; -import org.springframework.binding.expression.ParserContext; -import org.springframework.binding.expression.support.ParserContextImpl; -import org.springframework.binding.mapping.Mapper; -import org.springframework.binding.mapping.impl.DefaultMapper; -import org.springframework.binding.mapping.impl.DefaultMapping; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.AnnotationConfigUtils; -import org.springframework.context.support.GenericApplicationContext; -import org.springframework.core.JdkVersion; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; -import org.springframework.core.style.ToStringCreator; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import org.springframework.util.xml.DomUtils; -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.support.GenericWebApplicationContext; -import org.springframework.webflow.action.ActionResultExposer; -import org.springframework.webflow.action.EvaluateAction; -import org.springframework.webflow.action.ExternalRedirectAction; -import org.springframework.webflow.action.FlowDefinitionRedirectAction; -import org.springframework.webflow.action.RenderAction; -import org.springframework.webflow.action.SetAction; -import org.springframework.webflow.action.ViewFactoryActionAdapter; -import org.springframework.webflow.core.collection.AttributeMap; -import org.springframework.webflow.core.collection.LocalAttributeMap; -import org.springframework.webflow.core.collection.MutableAttributeMap; -import org.springframework.webflow.definition.registry.FlowDefinitionLocator; -import org.springframework.webflow.engine.Flow; -import org.springframework.webflow.engine.FlowExecutionExceptionHandler; -import org.springframework.webflow.engine.FlowVariable; -import org.springframework.webflow.engine.SubflowAttributeMapper; -import org.springframework.webflow.engine.TargetStateResolver; -import org.springframework.webflow.engine.Transition; -import org.springframework.webflow.engine.TransitionCriteria; -import org.springframework.webflow.engine.VariableValueFactory; -import org.springframework.webflow.engine.ViewVariable; -import org.springframework.webflow.engine.builder.FlowArtifactFactory; -import org.springframework.webflow.engine.builder.FlowBuilderException; -import org.springframework.webflow.engine.builder.support.AbstractFlowBuilder; -import org.springframework.webflow.engine.builder.support.ActionExecutingViewFactory; -import org.springframework.webflow.engine.support.BeanFactoryVariableValueFactory; -import org.springframework.webflow.engine.support.DefaultTransitionCriteria; -import org.springframework.webflow.engine.support.GenericSubflowAttributeMapper; -import org.springframework.webflow.engine.support.TransitionCriteriaChain; -import org.springframework.webflow.engine.support.TransitionExecutingFlowExecutionExceptionHandler; -import org.springframework.webflow.execution.Action; -import org.springframework.webflow.execution.RequestContext; -import org.springframework.webflow.execution.ScopeType; -import org.springframework.webflow.execution.ViewFactory; -import org.springframework.webflow.security.SecurityRule; -import org.springframework.webflow.util.ResourceHolder; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; - -/** - * Flow builder that builds flows as defined in an XML document. The XML document should adhere to the following format: - * - *

- *     <?xml version="1.0" encoding="UTF-8"?>
- *     <flow xmlns="http://www.springframework.org/schema/webflow"
- *           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- *           xsi:schemaLocation="http://www.springframework.org/schema/webflow
- *                               http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
- *         <!-- Define your states here -->
- *     </flow>
- * 
- * - *

- * Consult the web flow XML schema - * for more information on the XML-based flow definition format. - *

- * This builder will setup a flow-local bean factory for the flow being constructed. That flow-local bean factory will - * be populated with XML bean definitions contained in files referenced using the "import" element. The flow-local bean - * factory will use the bean factory of this flow builder as a parent. As such, the flow can access artifacts in either - * its flow-local bean factory or in the parent bean factory hierarchy, e.g. the bean factory of the dispatcher. - * - * @author Erwin Vervaet - * @author Keith Donald - * @author Scott Andrews - */ -public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolder { - - /** - * The resource from which the document element being parsed was read. Used as a location for relative resource - * lookup. - */ - protected Resource resource; - - /** - * A flow service locator local to this builder that first looks in a locally-managed Spring bean factory for - * services before searching the externally managed {@link #getFlowServiceLocator() service locator}. - */ - private LocalFlowBuilderContext localFlowBuilderContext; - - /** - * The loader for loading the flow definition resource XML document. - */ - private DocumentLoader documentLoader = new DefaultDocumentLoader(); - - /** - * The in-memory document object model (DOM) of the XML Document read from the flow definition resource. - */ - private Document document; - - /** - * Create a new XML flow builder parsing the document at the specified location, using the provided service locator - * to access externally managed flow artifacts. - * @param resource the location of the XML-based flow definition resource - */ - public XmlFlowBuilder(Resource resource) { - Assert.notNull(resource, "The resource location of the XML-based flow definition is required"); - this.resource = resource; - } - - /** - * Sets the loader that will load the XML-based flow definition document. Optional, defaults to - * {@link DefaultDocumentLoader}. - * @param documentLoader the document loader - */ - public void setDocumentLoader(DocumentLoader documentLoader) { - Assert.notNull(documentLoader, "The XML document loader is required"); - this.documentLoader = documentLoader; - } - - // implementing FlowBuilder - - protected void doInit() throws FlowBuilderException { - try { - document = documentLoader.loadDocument(resource); - initLocalFlowContext(getDocumentElement()); - } catch (IOException e) { - throw new FlowBuilderException("Could not access the XML flow definition resource at " + resource, e); - } catch (ParserConfigurationException e) { - throw new FlowBuilderException("Could not configure the parser to parse the XML flow definition at " - + resource, e); - } catch (SAXException e) { - throw new FlowBuilderException("Could not parse the XML flow definition document at " + resource, e); - } - } - - protected Flow createFlow() { - Flow flow = parseFlow(getDocumentElement()); - flow.setBeanFactory(getLocalContext().getBeanFactory()); - flow.setResourceLoader(getLocalContext().getResourceLoader()); - return flow; - } - - public void buildVariables() throws FlowBuilderException { - parseAndAddFlowVariables(getDocumentElement(), getFlow()); - } - - public void buildInputMapper() throws FlowBuilderException { - Mapper inputMapper = parseFlowInputMapper(getDocumentElement()); - if (inputMapper != null) { - getFlow().setInputMapper(inputMapper); - } - } - - public void buildStartActions() throws FlowBuilderException { - parseAndAddStartActions(getDocumentElement(), getFlow()); - } - - public void buildStates() throws FlowBuilderException { - parseAndAddStateDefinitions(getDocumentElement(), getFlow()); - } - - public void buildGlobalTransitions() throws FlowBuilderException { - parseAndAddGlobalTransitions(getDocumentElement(), getFlow()); - } - - public void buildEndActions() throws FlowBuilderException { - parseAndAddEndActions(getDocumentElement(), getFlow()); - } - - public void buildOutputMapper() throws FlowBuilderException { - Mapper outputMapper = parseFlowOutputMapper(getDocumentElement()); - if (outputMapper != null) { - getFlow().setOutputMapper(outputMapper); - } - } - - public void buildExceptionHandlers() throws FlowBuilderException { - getFlow().getExceptionHandlerSet().addAll(parseExceptionHandlers(getDocumentElement())); - } - - protected void doDispose() { - document = null; - } - - // implementing ResourceHolder - - public Resource getResource() { - return resource; - } - - // helpers - - /** - * Returns the DOM document parsed from the XML file. - */ - protected Document getDocument() { - return document; - } - - /** - * Returns the root document element. - */ - protected Element getDocumentElement() { - return document.getDocumentElement(); - } - - /** - * Returns the flow service locator local to this builder. - */ - protected LocalFlowBuilderContext getLocalContext() { - return localFlowBuilderContext; - } - - /** - * Returns the artifact factory of the flow service locator local to this builder. - */ - protected FlowArtifactFactory getFlowArtifactFactory() { - return getLocalContext().getFlowArtifactFactory(); - } - - // internal parsing logic and hook methods - - private Flow parseFlow(Element flowElement) { - if (!isRootFlowElement(flowElement)) { - throw new IllegalArgumentException("This is not the root 'flow' element"); - } - String flowId = getLocalContext().getFlowId(); - AttributeMap externallyAssignedAttributes = getLocalContext().getFlowAttributes(); - MutableAttributeMap flowAttributes = parseMetaAttributes(flowElement); - parseAndSetPersistenceContextAttribute(flowElement, flowAttributes); - parseAndSetSecuredAttribute(flowElement, flowAttributes); - return getFlowArtifactFactory().createFlow(flowId, flowAttributes.union(externallyAssignedAttributes)); - } - - private boolean isRootFlowElement(Element flowElement) { - return DomUtils.nodeNameEquals(flowElement, "flow"); - } - - private void parseAndSetPersistenceContextAttribute(Element flowElement, MutableAttributeMap flowAttributes) { - Element element = DomUtils.getChildElementByTagName(flowElement, "persistence-context"); - if (element != null) { - flowAttributes.put("persistenceContext", Boolean.TRUE); - } - } - - private void initLocalFlowContext(Element flowElement) { - List importElements = DomUtils.getChildElementsByTagName(flowElement, "bean-import"); - Resource[] resources = new Resource[importElements.size()]; - for (int i = 0; i < importElements.size(); i++) { - Element importElement = (Element) importElements.get(i); - try { - resources[i] = getResource().createRelative(importElement.getAttribute("resource")); - } catch (IOException e) { - throw new FlowBuilderException("Could not access flow-relative artifact resource '" - + importElement.getAttribute("resource") + "'", e); - } - } - this.localFlowBuilderContext = new LocalFlowBuilderContext(getContext(), - createFlowApplicationContext(resources)); - } - - private GenericApplicationContext createFlowApplicationContext(Resource[] resources) { - // see if this factory has a parent - BeanFactory parent = getContext().getBeanFactory(); - // determine the context implementation based on the current environment - GenericApplicationContext flowContext; - if (parent instanceof WebApplicationContext) { - GenericWebApplicationContext webContext = new GenericWebApplicationContext(); - webContext.setServletContext(((WebApplicationContext) parent).getServletContext()); - flowContext = webContext; - } else { - flowContext = new GenericApplicationContext(); - } - // set the parent if necessary - if (parent instanceof ApplicationContext) { - flowContext.setParent((ApplicationContext) parent); - } else { - if (parent != null) { - flowContext.getBeanFactory().setParentBeanFactory(parent); - } - } - flowContext.setResourceLoader(new FlowRelativeResourceLoader(resource)); - if (JdkVersion.isAtLeastJava15()) { - AnnotationConfigUtils.registerAnnotationConfigProcessors(flowContext); - } - new XmlBeanDefinitionReader(flowContext).loadBeanDefinitions(resources); - registerFlowBeans(flowContext.getDefaultListableBeanFactory()); - flowContext.refresh(); - return flowContext; - } - - /** - * Register beans in the bean factory local to the flow definition being built. - *

- * Subclasses may override this method to customize the population of the bean factory local to the flow definition - * being built; for example, to register mock implementations of services in a test environment. - * @param beanFactory the bean factory; register local beans with it using - * {@link ConfigurableBeanFactory#registerSingleton(String, Object)} - */ - protected void registerFlowBeans(ConfigurableBeanFactory beanFactory) { - } - - private void parseAndAddFlowVariables(Element flowElement, Flow flow) { - List varElements = DomUtils.getChildElementsByTagName(flowElement, "var"); - for (Iterator it = varElements.iterator(); it.hasNext();) { - flow.addVariable(parseFlowVariable((Element) it.next())); - } - } - - private FlowVariable parseFlowVariable(Element element) { - Class clazz = (Class) fromStringTo(Class.class).execute(element.getAttribute("class")); - VariableValueFactory valueFactory = new BeanFactoryVariableValueFactory(clazz, - (AutowireCapableBeanFactory) getFlow().getBeanFactory()); - ScopeType scope = parseScopeAttribute(element, ScopeType.FLOW); - if (!(scope == ScopeType.FLOW || scope == ScopeType.CONVERSATION)) { - throw new IllegalArgumentException("Only " + ScopeType.FLOW + " or " + ScopeType.CONVERSATION - + " scope is allowed for flow variables"); - } - return new FlowVariable(element.getAttribute("name"), valueFactory, scope == ScopeType.FLOW ? true : false); - } - - private ScopeType parseScopeAttribute(Element element, ScopeType defaultScope) { - if (element.hasAttribute("scope")) { - return (ScopeType) fromStringTo(ScopeType.class).execute(element.getAttribute("scope")); - } else { - return defaultScope; - } - } - - private Mapper parseFlowInputMapper(Element element) { - Collection inputs = DomUtils.getChildElementsByTagName(element, "input"); - if (inputs.size() == 0) { - return null; - } - DefaultMapper inputMapper = new DefaultMapper(); - for (Iterator it = inputs.iterator(); it.hasNext();) { - inputMapper.addMapping(parseFlowInputMapping((Element) it.next())); - } - return inputMapper; - } - - private DefaultMapping parseFlowInputMapping(Element element) { - ExpressionParser parser = getLocalContext().getExpressionParser(); - String name = element.getAttribute("name"); - String value = null; - if (element.hasAttribute("value")) { - value = element.getAttribute("value"); - } else { - value = name; - } - Expression source = parser.parseExpression(name, new ParserContextImpl().eval(MutableAttributeMap.class)); - Expression target = parser.parseExpression(value, new ParserContextImpl().eval(RequestContext.class)); - DefaultMapping mapping = new DefaultMapping(source, target); - parseAndSetMappingTypeConverter(element, mapping); - parseAndSetMappingRequired(element, mapping); - return mapping; - } - - private Mapper parseFlowOutputMapper(Element element) { - Collection inputs = DomUtils.getChildElementsByTagName(element, "output"); - if (inputs.size() == 0) { - return null; - } - DefaultMapper outputMapper = new DefaultMapper(); - for (Iterator it = inputs.iterator(); it.hasNext();) { - outputMapper.addMapping(parseFlowOutputMapping((Element) it.next())); - } - return outputMapper; - } - - private DefaultMapping parseFlowOutputMapping(Element element) { - ExpressionParser parser = getLocalContext().getExpressionParser(); - String name = element.getAttribute("name"); - String value = null; - if (element.hasAttribute("value")) { - value = element.getAttribute("value"); - } else { - value = name; - } - Expression source = parser.parseExpression(value, new ParserContextImpl().eval(RequestContext.class)); - Expression target = parser.parseExpression(name, new ParserContextImpl().eval(MutableAttributeMap.class)); - DefaultMapping mapping = new DefaultMapping(source, target); - parseAndSetMappingTypeConverter(element, mapping); - parseAndSetMappingRequired(element, mapping); - return mapping; - } - - private void parseAndSetMappingTypeConverter(Element element, DefaultMapping mapping) { - if (element.hasAttribute("type")) { - Class type = (Class) fromStringTo(Class.class).execute(element.getAttribute("type")); - ConversionExecutor typeConverter = new RuntimeBindingConversionExecutor(type, getConversionService()); - mapping.setTypeConverter(typeConverter); - } - } - - private void parseAndSetMappingRequired(Element element, DefaultMapping mapping) { - if (element.hasAttribute("required")) { - boolean required = ((Boolean) fromStringTo(Boolean.class).execute(element.getAttribute("required"))) - .booleanValue(); - mapping.setRequired(required); - } - } - - private void parseAndAddStartActions(Element element, Flow flow) { - Element startElement = DomUtils.getChildElementByTagName(element, "on-start"); - if (startElement != null) { - flow.getStartActionList().addAll(parseActions(startElement)); - } - } - - private void parseAndAddEndActions(Element element, Flow flow) { - Element endElement = DomUtils.getChildElementByTagName(element, "on-end"); - if (endElement != null) { - flow.getEndActionList().addAll(parseActions(endElement)); - } - } - - private void parseAndAddGlobalTransitions(Element element, Flow flow) { - Element globalTransitionsElement = DomUtils.getChildElementByTagName(element, "global-transitions"); - if (globalTransitionsElement != null) { - flow.getGlobalTransitionSet().addAll(parseTransitions(globalTransitionsElement)); - } - } - - private void parseAndAddStateDefinitions(Element flowElement, Flow flow) { - NodeList childNodeList = flowElement.getChildNodes(); - for (int i = 0; i < childNodeList.getLength(); i++) { - Node childNode = childNodeList.item(i); - if (childNode instanceof Element) { - Element stateElement = (Element) childNode; - if (DomUtils.nodeNameEquals(stateElement, "action-state")) { - parseAndAddActionState(stateElement, flow); - } else if (DomUtils.nodeNameEquals(stateElement, "view-state")) { - parseAndAddViewState(stateElement, flow); - } else if (DomUtils.nodeNameEquals(stateElement, "decision-state")) { - parseAndAddDecisionState(stateElement, flow); - } else if (DomUtils.nodeNameEquals(stateElement, "subflow-state")) { - parseAndAddSubflowState(stateElement, flow); - } else if (DomUtils.nodeNameEquals(stateElement, "end-state")) { - parseAndAddEndState(stateElement, flow); - } - } - } - parseAndSetStartState(flowElement, flow); - } - - private void parseAndSetStartState(Element element, Flow flow) { - String startStateId = getStartStateId(element); - if (StringUtils.hasText(startStateId)) { - flow.setStartState(startStateId); - } - } - - private String getStartStateId(Element element) { - String startState = "start-state"; - if (element.hasAttribute(startState)) { - return element.getAttribute(startState); - } else { - return null; - } - } - - private void parseAndAddActionState(Element element, Flow flow) { - MutableAttributeMap attributes = parseMetaAttributes(element); - parseAndSetSecuredAttribute(element, attributes); - getFlowArtifactFactory().createActionState(parseId(element), flow, parseEntryActions(element), - parseActions(element), parseTransitions(element), parseExceptionHandlers(element), - parseExitActions(element), attributes); - } - - private void parseAndAddViewState(Element element, Flow flow) { - ViewFactory viewFactory = parseViewFactory(element, false); - Boolean redirect = null; - if (element.hasAttribute("redirect")) { - redirect = (Boolean) fromStringTo(Boolean.class).execute(element.getAttribute("redirect")); - } - boolean popup = false; - if (element.hasAttribute("popup")) { - popup = ((Boolean) fromStringTo(Boolean.class).execute(element.getAttribute("popup"))).booleanValue(); - } - MutableAttributeMap attributes = parseMetaAttributes(element); - parseAndSetSecuredAttribute(element, attributes); - getFlowArtifactFactory().createViewState(parseId(element), flow, parseViewVariables(element), - parseEntryActions(element), viewFactory, redirect, popup, parseRenderActions(element), - parseTransitions(element), parseExceptionHandlers(element), parseExitActions(element), attributes); - } - - private void parseAndAddDecisionState(Element element, Flow flow) { - MutableAttributeMap attributes = parseMetaAttributes(element); - parseAndSetSecuredAttribute(element, attributes); - getFlowArtifactFactory().createDecisionState(parseId(element), flow, parseEntryActions(element), - parseIfs(element), parseExceptionHandlers(element), parseExitActions(element), attributes); - } - - private void parseAndAddSubflowState(Element element, Flow flow) { - MutableAttributeMap attributes = parseMetaAttributes(element); - parseAndSetSecuredAttribute(element, attributes); - getFlowArtifactFactory().createSubflowState(parseId(element), flow, parseEntryActions(element), - parseSubflowExpression(element), parseSubflowAttributeMapper(element), parseTransitions(element), - parseExceptionHandlers(element), parseExitActions(element), attributes); - } - - private Expression parseSubflowExpression(Element element) { - String subflow = element.getAttribute("subflow"); - Expression subflowId = getExpressionParser().parseExpression(subflow, - new ParserContextImpl().template().eval(RequestContext.class).expect(String.class)); - return new SubflowExpression(subflowId, getLocalContext().getFlowDefinitionLocator()); - } - - private void parseAndAddEndState(Element element, Flow flow) { - MutableAttributeMap attributes = parseMetaAttributes(element); - if (element.hasAttribute("commit")) { - attributes.put("commit", fromStringTo(Boolean.class).execute(element.getAttribute("commit"))); - } - parseAndSetSecuredAttribute(element, attributes); - getFlowArtifactFactory().createEndState(parseId(element), flow, parseEntryActions(element), - new ViewFactoryActionAdapter(parseViewFactory(element, true)), parseFlowOutputMapper(element), - parseExceptionHandlers(element), attributes); - } - - private String parseId(Element element) { - return element.getAttribute("id"); - } - - private ViewVariable[] parseViewVariables(Element viewStateElement) { - List varElements = DomUtils.getChildElementsByTagName(viewStateElement, "var"); - List variables = new ArrayList(varElements.size()); - for (Iterator it = varElements.iterator(); it.hasNext();) { - variables.add(parseViewVariable((Element) it.next())); - } - return (ViewVariable[]) variables.toArray(new ViewVariable[variables.size()]); - } - - private ViewVariable parseViewVariable(Element element) { - Class clazz = (Class) fromStringTo(Class.class).execute(element.getAttribute("class")); - VariableValueFactory valueFactory = new BeanFactoryVariableValueFactory(clazz, - (AutowireCapableBeanFactory) getFlow().getBeanFactory()); - return new ViewVariable(element.getAttribute("name"), valueFactory); - } - - private Action[] parseEntryActions(Element element) { - Element entryActionsElement = DomUtils.getChildElementByTagName(element, "on-entry"); - if (entryActionsElement != null) { - return parseActions(entryActionsElement); - } else { - return null; - } - } - - private ViewFactory parseViewFactory(Element element, boolean endState) { - String encodedView = element.getAttribute("view"); - if (!StringUtils.hasText(encodedView)) { - if (endState) { - return null; - } else { - encodedView = getLocalContext().getViewFactoryCreator().getViewIdByConvention(parseId(element)); - Expression viewId = getExpressionParser().parseExpression(encodedView, - new ParserContextImpl().template().eval(RequestContext.class).expect(String.class)); - return createViewFactory(element, viewId); - } - } else if (encodedView.startsWith("externalRedirect:")) { - String encodedUrl = encodedView.substring("externalRedirect:".length()); - Expression externalUrl = getExpressionParser().parseExpression(encodedUrl, - new ParserContextImpl().template().eval(RequestContext.class).expect(String.class)); - return new ActionExecutingViewFactory(new ExternalRedirectAction(externalUrl)); - } else if (encodedView.startsWith("flowRedirect:")) { - String flowRedirect = encodedView.substring("flowRedirect:".length()); - Expression expression = getExpressionParser().parseExpression(flowRedirect, - new ParserContextImpl().template().eval(RequestContext.class).expect(String.class)); - return new ActionExecutingViewFactory(new FlowDefinitionRedirectAction(expression)); - } else { - Expression viewId = getExpressionParser().parseExpression(encodedView, - new ParserContextImpl().template().eval(RequestContext.class).expect(String.class)); - return createViewFactory(element, viewId); - } - } - - private ViewFactory createViewFactory(Element element, Expression viewId) { - return getLocalContext().getViewFactoryCreator().createViewFactory(viewId, getExpressionParser(), - getLocalContext().getFormatterRegistry(), getLocalContext().getResourceLoader()); - } - - private Action[] parseRenderActions(Element element) { - Element renderActionsElement = DomUtils.getChildElementByTagName(element, "on-render"); - if (renderActionsElement != null) { - return parseActions(renderActionsElement); - } else { - return null; - } - } - - private Action[] parseExitActions(Element element) { - Element exitActionsElement = DomUtils.getChildElementByTagName(element, "on-exit"); - if (exitActionsElement != null) { - return parseActions(exitActionsElement); - } else { - return null; - } - } - - private Transition[] parseTransitions(Element element) { - List transitions = new LinkedList(); - List transitionElements = DomUtils.getChildElementsByTagName(element, "transition"); - for (Iterator it = transitionElements.iterator(); it.hasNext();) { - Element transitionElement = (Element) it.next(); - if (!StringUtils.hasText(transitionElement.getAttribute("on-exception"))) { - transitions.add(parseTransition(transitionElement)); - } - } - return (Transition[]) transitions.toArray(new Transition[transitions.size()]); - } - - private Transition parseTransition(Element element) { - TransitionCriteria matchingCriteria = (TransitionCriteria) fromStringTo(TransitionCriteria.class).execute( - element.getAttribute("on")); - TargetStateResolver targetStateResolver = (TargetStateResolver) fromStringTo(TargetStateResolver.class) - .execute(element.getAttribute("to")); - TransitionCriteria executionCriteria = TransitionCriteriaChain.criteriaChainFor(parseActions(element)); - MutableAttributeMap attributes = parseMetaAttributes(element); - parseAndSetSecuredAttribute(element, attributes); - return getFlowArtifactFactory().createTransition(targetStateResolver, matchingCriteria, executionCriteria, - attributes); - } - - private Action[] parseActions(Element element) { - List actions = new LinkedList(); - NodeList childNodeList = element.getChildNodes(); - for (int i = 0; i < childNodeList.getLength(); i++) { - Node childNode = childNodeList.item(i); - if (!(childNode instanceof Element)) { - continue; - } - if (DomUtils.nodeNameEquals(childNode, "evaluate")) { - actions.add(parseEvaluateAction((Element) childNode)); - } else if (DomUtils.nodeNameEquals(childNode, "render")) { - actions.add(parseRenderAction((Element) childNode)); - } else if (DomUtils.nodeNameEquals(childNode, "set")) { - actions.add(parseSetAction((Element) childNode)); - } - } - return (Action[]) actions.toArray(new Action[actions.size()]); - } - - private Action parseEvaluateAction(Element element) { - String expressionString = element.getAttribute("expression"); - Expression expression = getExpressionParser().parseExpression(expressionString, - new ParserContextImpl().eval(RequestContext.class)); - return new EvaluateAction(expression, parseEvaluationActionResultExposer(element)); - } - - private ActionResultExposer parseEvaluationActionResultExposer(Element element) { - if (element.hasAttribute("result")) { - String resultExpressionString = element.getAttribute("result"); - Expression resultExpression = getExpressionParser().parseExpression(resultExpressionString, - new ParserContextImpl().eval(RequestContext.class)); - Class expectedResultType = null; - if (element.hasAttribute("result-type")) { - expectedResultType = (Class) fromStringTo(Class.class).execute(element.getAttribute("result-type")); - } - return new ActionResultExposer(resultExpression, expectedResultType, getConversionService()); - } else { - return null; - } - } - - private Action parseRenderAction(Element element) { - String[] fragmentExpressionStrings = StringUtils.commaDelimitedListToStringArray(element - .getAttribute("fragments")); - fragmentExpressionStrings = StringUtils.trimArrayElements(fragmentExpressionStrings); - ExpressionParser parser = getExpressionParser(); - ParserContext context = new ParserContextImpl().template().eval(RequestContext.class).expect(String.class); - Expression[] fragments = new Expression[fragmentExpressionStrings.length]; - for (int i = 0; i < fragmentExpressionStrings.length; i++) { - String fragment = fragmentExpressionStrings[i]; - fragments[i] = parser.parseExpression(fragment, context); - } - return new RenderAction(fragments); - } - - private Action parseSetAction(Element element) { - Expression nameExpression = getExpressionParser().parseExpression(element.getAttribute("name"), - new ParserContextImpl().eval(RequestContext.class)); - Expression valueExpression = getExpressionParser().parseExpression(element.getAttribute("value"), - new ParserContextImpl().eval(RequestContext.class)); - Class expectedType = null; - if (element.hasAttribute("type")) { - expectedType = (Class) fromStringTo(Class.class).execute(element.getAttribute("type")); - } - return new SetAction(nameExpression, valueExpression, expectedType, getConversionService()); - } - - private MutableAttributeMap parseMetaAttributes(Element element) { - LocalAttributeMap attributes = new LocalAttributeMap(); - List propertyElements = DomUtils.getChildElementsByTagName(element, "attribute"); - for (int i = 0; i < propertyElements.size(); i++) { - parseAndSetMetaAttribute((Element) propertyElements.get(i), attributes); - } - return attributes; - } - - private void parseAndSetMetaAttribute(Element element, MutableAttributeMap attributes) { - String name = element.getAttribute("name"); - String value = null; - if (element.hasAttribute("value")) { - value = element.getAttribute("value"); - } else { - List valueElements = DomUtils.getChildElementsByTagName(element, "value"); - Assert.state(valueElements.size() == 1, "A property value should be specified for property '" + name + "'"); - value = DomUtils.getTextValue((Element) valueElements.get(0)); - } - attributes.put(name, convertAttributeValueIfNecessary(element, value)); - } - - private Object convertAttributeValueIfNecessary(Element element, String stringValue) { - if (element.hasAttribute("type")) { - Class targetClass = (Class) fromStringTo(Class.class).execute(element.getAttribute("type")); - return fromStringTo(targetClass).execute(stringValue); - } else { - return stringValue; - } - } - - private Transition[] parseIfs(Element element) { - List transitions = new LinkedList(); - List transitionElements = DomUtils.getChildElementsByTagName(element, "if"); - for (Iterator it = transitionElements.iterator(); it.hasNext();) { - transitions.addAll(Arrays.asList(parseIf((Element) it.next()))); - } - return (Transition[]) transitions.toArray(new Transition[transitions.size()]); - } - - private Transition[] parseIf(Element element) { - Transition thenTransition = parseThen(element); - if (StringUtils.hasText(element.getAttribute("else"))) { - Transition elseTransition = parseElse(element); - return new Transition[] { thenTransition, elseTransition }; - } else { - return new Transition[] { thenTransition }; - } - } - - private Transition parseThen(Element element) { - Expression expression = getExpressionParser().parseExpression(element.getAttribute("test"), - new ParserContextImpl().eval(RequestContext.class).expect(Boolean.class)); - TransitionCriteria matchingCriteria = new DefaultTransitionCriteria(expression); - TargetStateResolver targetStateResolver = (TargetStateResolver) fromStringTo(TargetStateResolver.class) - .execute(element.getAttribute("then")); - return getFlowArtifactFactory().createTransition(targetStateResolver, matchingCriteria, null, null); - } - - private Transition parseElse(Element element) { - TargetStateResolver targetStateResolver = (TargetStateResolver) fromStringTo(TargetStateResolver.class) - .execute(element.getAttribute("else")); - return getFlowArtifactFactory().createTransition(targetStateResolver, null, null, null); - } - - private SubflowAttributeMapper parseSubflowAttributeMapper(Element element) { - if (element.hasAttribute("subflow-attribute-mapper")) { - String attributeMapperBeanId = element.getAttribute("subflow-attribute-mapper"); - return (SubflowAttributeMapper) getLocalContext().getBeanFactory().getBean(attributeMapperBeanId, - SubflowAttributeMapper.class); - } else { - Mapper inputMapper = parseSubflowInputMapper(element); - Mapper outputMapper = parseSubflowOutputMapper(element); - return new GenericSubflowAttributeMapper(inputMapper, outputMapper); - } - } - - private Mapper parseSubflowInputMapper(Element element) { - Collection inputs = DomUtils.getChildElementsByTagName(element, "input"); - if (inputs.size() == 0) { - return null; - } - DefaultMapper inputMapper = new DefaultMapper(); - for (Iterator it = inputs.iterator(); it.hasNext();) { - inputMapper.addMapping(parseSubflowInputMapping((Element) it.next())); - } - return inputMapper; - } - - private DefaultMapping parseSubflowInputMapping(Element element) { - ExpressionParser parser = getLocalContext().getExpressionParser(); - String name = element.getAttribute("name"); - String value = null; - if (element.hasAttribute("value")) { - value = element.getAttribute("value"); - } else { - value = name; - } - Expression source = parser.parseExpression(value, new ParserContextImpl().eval(RequestContext.class)); - Expression target = parser.parseExpression(name, new ParserContextImpl().eval(MutableAttributeMap.class)); - DefaultMapping mapping = new DefaultMapping(source, target); - parseAndSetMappingTypeConverter(element, mapping); - parseAndSetMappingRequired(element, mapping); - return mapping; - } - - private Mapper parseSubflowOutputMapper(Element element) { - Collection inputs = DomUtils.getChildElementsByTagName(element, "output"); - if (inputs.size() == 0) { - return null; - } - DefaultMapper outputMapper = new DefaultMapper(); - for (Iterator it = inputs.iterator(); it.hasNext();) { - outputMapper.addMapping(parseSubflowOutputMapping((Element) it.next())); - } - return outputMapper; - } - - private DefaultMapping parseSubflowOutputMapping(Element element) { - ExpressionParser parser = getLocalContext().getExpressionParser(); - String name = element.getAttribute("name"); - String value = null; - if (element.hasAttribute("value")) { - value = element.getAttribute("value"); - } else { - value = name; - } - Expression source = parser.parseExpression(name, new ParserContextImpl().eval(MutableAttributeMap.class)); - Expression target = parser.parseExpression(value, new ParserContextImpl().eval(RequestContext.class)); - DefaultMapping mapping = new DefaultMapping(source, target); - parseAndSetMappingTypeConverter(element, mapping); - parseAndSetMappingRequired(element, mapping); - return mapping; - } - - private FlowExecutionExceptionHandler[] parseExceptionHandlers(Element element) { - FlowExecutionExceptionHandler[] transitionExecutingHandlers = parseTransitionExecutingExceptionHandlers(element); - FlowExecutionExceptionHandler[] customHandlers = parseCustomExceptionHandlers(element); - FlowExecutionExceptionHandler[] exceptionHandlers = new FlowExecutionExceptionHandler[transitionExecutingHandlers.length - + customHandlers.length]; - System.arraycopy(transitionExecutingHandlers, 0, exceptionHandlers, 0, transitionExecutingHandlers.length); - System.arraycopy(customHandlers, 0, exceptionHandlers, transitionExecutingHandlers.length, - customHandlers.length); - return exceptionHandlers; - } - - private FlowExecutionExceptionHandler[] parseTransitionExecutingExceptionHandlers(Element element) { - List transitionElements = Collections.EMPTY_LIST; - if (isRootFlowElement(element)) { - Element globalTransitionsElement = DomUtils.getChildElementByTagName(element, "global-transitions"); - if (globalTransitionsElement != null) { - transitionElements = DomUtils.getChildElementsByTagName(globalTransitionsElement, "transition"); - } - } else { - transitionElements = DomUtils.getChildElementsByTagName(element, "transition"); - } - List exceptionHandlers = new LinkedList(); - for (Iterator it = transitionElements.iterator(); it.hasNext();) { - Element transitionElement = (Element) it.next(); - if (StringUtils.hasText(transitionElement.getAttribute("on-exception"))) { - exceptionHandlers.add(parseTransitionExecutingExceptionHandler(transitionElement)); - } - } - return (FlowExecutionExceptionHandler[]) exceptionHandlers - .toArray(new FlowExecutionExceptionHandler[exceptionHandlers.size()]); - } - - private FlowExecutionExceptionHandler parseTransitionExecutingExceptionHandler(Element element) { - TransitionExecutingFlowExecutionExceptionHandler handler = new TransitionExecutingFlowExecutionExceptionHandler(); - Class exceptionClass = (Class) fromStringTo(Class.class).execute(element.getAttribute("on-exception")); - TargetStateResolver targetStateResolver = (TargetStateResolver) fromStringTo(TargetStateResolver.class) - .execute(element.getAttribute("to")); - handler.add(exceptionClass, targetStateResolver); - handler.getActionList().addAll(parseActions(element)); - return handler; - } - - private FlowExecutionExceptionHandler[] parseCustomExceptionHandlers(Element element) { - List exceptionHandlers = new LinkedList(); - List handlerElements = DomUtils.getChildElementsByTagName(element, "exception-handler"); - for (int i = 0; i < handlerElements.size(); i++) { - Element handlerElement = (Element) handlerElements.get(i); - exceptionHandlers.add(parseCustomExceptionHandler(handlerElement)); - } - return (FlowExecutionExceptionHandler[]) exceptionHandlers - .toArray(new FlowExecutionExceptionHandler[exceptionHandlers.size()]); - } - - private FlowExecutionExceptionHandler parseCustomExceptionHandler(Element element) { - return (FlowExecutionExceptionHandler) getLocalContext().getBeanFactory().getBean(element.getAttribute("bean"), - FlowExecutionExceptionHandler.class); - } - - private void parseAndSetSecuredAttribute(Element element, MutableAttributeMap attributes) { - Element secured = DomUtils.getChildElementByTagName(element, "secured"); - if (secured != null) { - SecurityRule rule = new SecurityRule(); - rule.setAttributes(SecurityRule.convertAttributesFromCommaSeparatedString(secured - .getAttribute("attributes"))); - String comparisonType = secured.getAttribute("match"); - if ("any".equals(comparisonType)) { - rule.setComparisonType(SecurityRule.COMPARISON_ANY); - } else if ("all".equals(comparisonType)) { - rule.setComparisonType(SecurityRule.COMPARISON_ALL); - } else { - // default to any - rule.setComparisonType(SecurityRule.COMPARISON_ANY); - } - attributes.put(SecurityRule.SECURITY_ATTRIBUTE_NAME, rule); - } - } - - private ConversionExecutor fromStringTo(Class targetType) throws ConversionException { - return getConversionService().getConversionExecutor(String.class, targetType); - } - - private ExpressionParser getExpressionParser() { - return getLocalContext().getExpressionParser(); - } - - private ConversionService getConversionService() { - return getLocalContext().getConversionService(); - } - - private static class FlowRelativeResourceLoader implements ResourceLoader { - private Resource resource; - - public FlowRelativeResourceLoader(Resource resource) { - this.resource = resource; - } - - public ClassLoader getClassLoader() { - return resource.getClass().getClassLoader(); - } - - public Resource getResource(String location) { - try { - return resource.createRelative(location); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - - private static class SubflowExpression implements Expression { - - private Expression subflowId; - - private FlowDefinitionLocator flowDefinitionLocator; - - public SubflowExpression(Expression subflowId, FlowDefinitionLocator flowDefinitionLocator) { - this.subflowId = subflowId; - this.flowDefinitionLocator = flowDefinitionLocator; - } - - public Object getValue(Object context) throws EvaluationException { - String subflowId = (String) this.subflowId.getValue(context); - return flowDefinitionLocator.getFlowDefinition(subflowId); - } - - public void setValue(Object context, Object value) throws EvaluationException { - throw new UnsupportedOperationException("Cannot set a subflow expression"); - } - - public Class getValueType(Object context) { - return null; - } - - public String getExpressionString() { - return null; - } - } - - public String toString() { - return new ToStringCreator(this).append("location", resource).toString(); - } -} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/package.html b/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/package.html deleted file mode 100644 index 714eaedd..00000000 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/package.html +++ /dev/null @@ -1,7 +0,0 @@ - - -

-The XML-based flow builder implementation. -

- - \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/AbstractActionModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/AbstractActionModel.java new file mode 100644 index 00000000..d1e0b4cc --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/AbstractActionModel.java @@ -0,0 +1,26 @@ +/* + * 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.model; + + +/** + * Model support for actions. + * + * @author Scott Andrews + */ +public abstract class AbstractActionModel extends AbstractModel { + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/AbstractMappingModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/AbstractMappingModel.java new file mode 100644 index 00000000..ddb5f9c4 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/AbstractMappingModel.java @@ -0,0 +1,103 @@ +/* + * 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.model; + +import org.springframework.util.StringUtils; + +/** + * Model support for mappings. + * + * @author Scott Andrews + */ +public abstract class AbstractMappingModel extends AbstractModel { + + private String name; + private String value; + private String type; + private String required; + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @param name the name to set + */ + public void setName(String name) { + if (StringUtils.hasText(name)) { + this.name = name; + } else { + this.name = null; + } + } + + /** + * @return the value + */ + public String getValue() { + return value; + } + + /** + * @param value the value to set + */ + public void setValue(String value) { + if (StringUtils.hasText(value)) { + this.value = value; + } else { + this.value = null; + } + } + + /** + * @return the type + */ + public String getType() { + return type; + } + + /** + * @param type the type to set + */ + public void setType(String type) { + if (StringUtils.hasText(type)) { + this.type = type; + } else { + this.type = null; + } + } + + /** + * @return the required + */ + public String getRequired() { + return required; + } + + /** + * @param required the required to set + */ + public void setRequired(String required) { + if (StringUtils.hasText(required)) { + this.required = required; + } else { + this.required = null; + } + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/AbstractModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/AbstractModel.java new file mode 100644 index 00000000..4eb3b5d5 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/AbstractModel.java @@ -0,0 +1,120 @@ +/* + * 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.model; + +import java.util.Iterator; +import java.util.LinkedList; + +/** + * Contains basic merge functions that can be utilized by other models. + * + * @author Scott Andrews + */ +public abstract class AbstractModel implements Model { + + /** + * Merge two objects. If the child is null, the parent will be returned. Else the child will be returned. + * @param child the child object to merge + * @param parent the parent object to merge + * @return the merged string + */ + protected Object merge(Object child, Object parent) { + if (child == null) { + return parent; + } else { + return child; + } + } + + /** + * Merge two strings. If the child is null, the parent will be returned. Else the child will be returned. + * @param child the child string to merge + * @param parent the parent string to merge + * @return the merged string + */ + protected String merge(String child, String parent) { + return (String) merge((Object) child, (Object) parent); + } + + /** + * Merge two model elements. If the child is null, the parent will be returned. Else the parent element will be + * merged into the child element with the result returned + * @param child the child model element to merge + * @param parent the parent model element to merge + * @return the merged element model + */ + protected Model merge(Model child, Model parent) { + if (child == null) { + return parent; + } else if (parent == null) { + return child; + } else { + child.merge(parent); + return child; + } + } + + /** + * Merge two lists. All child element will be in the merged list. All parent elements not in the child list will be + * added. Mergeable elements in both lists will be merged according to that element merge rules. New items are added + * to the end of the list + * @param child the child list to merge + * @param parent the parent list to merge + * @return the merged list + */ + protected LinkedList merge(LinkedList child, LinkedList parent) { + return merge(child, parent, true); + } + + /** + * Merge two lists. All child element will be in the merged list. All parent elements not in the child list will be + * added. Mergeable elements in both lists will be merged according to that element merge rules. + * @param child the child list to merge + * @param parent the parent list to merge + * @param addAtEnd if true new items will be added at the end of the list, otherwise the beginning + * @return the merged list + */ + protected LinkedList merge(LinkedList child, LinkedList parent, boolean addAtEnd) { + if (parent == null) { + return child; + } else if (child == null) { + return parent; + } else { + for (Iterator parentIt = parent.iterator(); parentIt.hasNext();) { + Model parentElement = (Model) parentIt.next(); + if (!child.contains(parentElement)) { + boolean matchFound = false; + for (Iterator childIt = child.iterator(); !matchFound && childIt.hasNext();) { + Model childElement = (Model) childIt.next(); + if (childElement.isMergeableWith(parentElement)) { + matchFound = true; + childElement.merge(parentElement); + } + } + if (!matchFound) { + if (addAtEnd) { + child.addLast(parentElement); + } else { + child.addFirst(parentElement); + } + } + } + } + } + return child; + } + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/AbstractStateModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/AbstractStateModel.java new file mode 100644 index 00000000..d2e2f262 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/AbstractStateModel.java @@ -0,0 +1,185 @@ +/* + * 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.model; + +import java.util.LinkedList; + +import org.springframework.util.StringUtils; + +/** + * Model support for states. + * + * @author Scott Andrews + */ +public abstract class AbstractStateModel extends AbstractModel { + private String id; + private LinkedList attributes; + private SecuredModel secured; + private LinkedList onEntryActions; + private LinkedList exceptionHandlers; + + /** + * @return the id + */ + public String getId() { + return id; + } + + /** + * @param id the id to set + */ + public void setId(String id) { + if (StringUtils.hasText(id)) { + this.id = id; + } else { + this.id = null; + } + } + + /** + * @return the attributes + */ + public LinkedList getAttributes() { + return attributes; + } + + /** + * @param attributes the attributes to set + */ + public void setAttributes(LinkedList attributes) { + this.attributes = attributes; + } + + /** + * @param attribute the attribute to add + */ + public void addAttribute(AttributeModel attribute) { + if (attribute == null) { + return; + } + if (attributes == null) { + attributes = new LinkedList(); + } + attributes.add(attribute); + } + + /** + * @param attributes the attributes to add + */ + public void addAttributes(LinkedList attributes) { + if (attributes == null || attributes.isEmpty()) { + return; + } + if (this.attributes == null) { + this.attributes = new LinkedList(); + } + this.attributes.addAll(attributes); + } + + /** + * @return the secured + */ + public SecuredModel getSecured() { + return secured; + } + + /** + * @param secured the secured to set + */ + public void setSecured(SecuredModel secured) { + this.secured = secured; + } + + /** + * @return the on entry actions + */ + public LinkedList getOnEntryActions() { + return onEntryActions; + } + + /** + * @param onEntryActions the on entry actions to set + */ + public void setOnEntryActions(LinkedList onEntryActions) { + this.onEntryActions = onEntryActions; + } + + /** + * @param onEntryAction the on entry action to add + */ + public void addOnEntryAction(AbstractActionModel onEntryAction) { + if (onEntryAction == null) { + return; + } + if (onEntryActions == null) { + onEntryActions = new LinkedList(); + } + onEntryActions.add(onEntryAction); + } + + /** + * @param onEntryActions the on entry actions to add + */ + public void addOnEntryActions(LinkedList onEntryActions) { + if (onEntryActions == null || onEntryActions.isEmpty()) { + return; + } + if (this.onEntryActions == null) { + this.onEntryActions = new LinkedList(); + } + this.onEntryActions.addAll(onEntryActions); + } + + /** + * @return the exception handlers + */ + public LinkedList getExceptionHandlers() { + return exceptionHandlers; + } + + /** + * @param exceptionHandlers the exception handlers to set + */ + public void setExceptionHandlers(LinkedList exceptionHandlers) { + this.exceptionHandlers = exceptionHandlers; + } + + /** + * @param exceptionHandler the exception handler to add + */ + public void addExceptionHandler(ExceptionHandlerModel exceptionHandler) { + if (exceptionHandler == null) { + return; + } + if (exceptionHandlers == null) { + exceptionHandlers = new LinkedList(); + } + exceptionHandlers.add(exceptionHandler); + } + + /** + * @param exceptionHandlers the exception handlers to add + */ + public void addExceptionHandlers(LinkedList exceptionHandlers) { + if (exceptionHandlers == null || exceptionHandlers.isEmpty()) { + return; + } + if (this.exceptionHandlers == null) { + this.exceptionHandlers = new LinkedList(); + } + this.exceptionHandlers.addAll(exceptionHandlers); + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/AbstractTransitionableStateModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/AbstractTransitionableStateModel.java new file mode 100644 index 00000000..5015b734 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/AbstractTransitionableStateModel.java @@ -0,0 +1,108 @@ +/* + * 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.model; + +import java.util.LinkedList; + +/** + * Model support for transitionable states. + * + * @author Scott Andrews + */ +public abstract class AbstractTransitionableStateModel extends AbstractStateModel { + private LinkedList transitions; + private LinkedList onExitActions; + + /** + * @return the transitions + */ + public LinkedList getTransitions() { + return transitions; + } + + /** + * @param transitions the transitions to set + */ + public void setTransitions(LinkedList transitions) { + this.transitions = transitions; + } + + /** + * @param transition the transition to add + */ + public void addTransition(TransitionModel transition) { + if (transition == null) { + return; + } + if (transitions == null) { + transitions = new LinkedList(); + } + transitions.add(transition); + } + + /** + * @param transitions the transitions to add + */ + public void addTransitions(LinkedList transitions) { + if (transitions == null || transitions.isEmpty()) { + return; + } + if (this.transitions == null) { + this.transitions = new LinkedList(); + } + this.transitions.addAll(transitions); + } + + /** + * @return the on exit actions + */ + public LinkedList getOnExitActions() { + return onExitActions; + } + + /** + * @param onExitActions the on exit actions to set + */ + public void setOnExitActions(LinkedList onExitActions) { + this.onExitActions = onExitActions; + } + + /** + * @param onExitAction the on exit action to add + */ + public void addOnExitAction(AbstractActionModel onExitAction) { + if (onExitAction == null) { + return; + } + if (this.onExitActions == null) { + this.onExitActions = new LinkedList(); + } + this.onExitActions.add(onExitAction); + } + + /** + * @param onExitActions the on exit actions to add + */ + public void addOnExitsActions(LinkedList onExitActions) { + if (onExitActions == null || onExitActions.isEmpty()) { + return; + } + if (this.onExitActions == null) { + this.onExitActions = new LinkedList(); + } + this.onExitActions.addAll(onExitActions); + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/ActionStateModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/ActionStateModel.java new file mode 100644 index 00000000..f132abde --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/ActionStateModel.java @@ -0,0 +1,174 @@ +/* + * 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.model; + +import java.util.LinkedList; + +import org.springframework.util.ObjectUtils; + +/** + * Model support for action states. + *

+ * A state where one or more actions are executed. This state type is typically used to invoke application code. An + * action state is a transitionable state. A transition out of this state is driven by the result of action execution. + * + * @author Scott Andrews + */ +public class ActionStateModel extends AbstractTransitionableStateModel { + private LinkedList actions; + + /** + * Create an action state model + * @param id the state identifier + */ + public ActionStateModel(String id) { + setId(id); + } + + /** + * Create an action state model + * @param id the state identifier + * @param attributes meta attributes for the state + * @param secured security settings for the state + * @param onEntryActions actions to execute upon entry + * @param transitions transitions for the state + * @param onExitActions actions to execute before leaving the state + * @param actions actions to execute during the state + * @param exceptionHandlers exception handlers for the state + */ + public ActionStateModel(String id, LinkedList attributes, SecuredModel secured, LinkedList onEntryActions, + LinkedList transitions, LinkedList onExitActions, LinkedList actions, LinkedList exceptionHandlers) { + setId(id); + setAttributes(attributes); + setSecured(secured); + setOnEntryActions(onEntryActions); + setTransitions(transitions); + setOnExitActions(onExitActions); + setActions(actions); + setExceptionHandlers(exceptionHandlers); + } + + /** + * Merge properties + * @param model the action state to merge into this state + */ + public void merge(Model model) { + if (isMergeableWith(model)) { + ActionStateModel state = (ActionStateModel) model; + setAttributes(merge(getAttributes(), state.getAttributes())); + setSecured((SecuredModel) merge(getSecured(), state.getSecured())); + setOnEntryActions(merge(getOnEntryActions(), state.getOnEntryActions(), false)); + setExceptionHandlers(merge(getExceptionHandlers(), state.getExceptionHandlers())); + setTransitions(merge(getTransitions(), state.getTransitions())); + setOnExitActions(merge(getOnExitActions(), state.getOnExitActions(), false)); + setActions(merge(getActions(), state.getActions(), false)); + } + } + + /** + * Tests if the model is able to be merged with this action state + * @param model the model to test + */ + public boolean isMergeableWith(Model model) { + if (model == null) { + return false; + } + if (!(model instanceof ActionStateModel)) { + return false; + } + ActionStateModel state = (ActionStateModel) model; + return ObjectUtils.nullSafeEquals(getId(), state.getId()); + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (!(obj instanceof ActionStateModel)) { + return false; + } + ActionStateModel state = (ActionStateModel) obj; + if (state == null) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getId(), state.getId())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getAttributes(), state.getAttributes())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getSecured(), state.getSecured())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getOnEntryActions(), state.getOnEntryActions())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getExceptionHandlers(), state.getExceptionHandlers())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getTransitions(), state.getTransitions())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getOnExitActions(), state.getOnExitActions())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getActions(), state.getActions())) { + return false; + } else { + return true; + } + } + + public int hashCode() { + return ObjectUtils.nullSafeHashCode(getId()) * 27 + ObjectUtils.nullSafeHashCode(getAttributes()) * 27 + + ObjectUtils.nullSafeHashCode(getSecured()) * 27 + ObjectUtils.nullSafeHashCode(getOnEntryActions()) + * 27 + ObjectUtils.nullSafeHashCode(getExceptionHandlers()) * 27 + + ObjectUtils.nullSafeHashCode(getTransitions()) * 27 + + ObjectUtils.nullSafeHashCode(getOnExitActions()) * 27 + ObjectUtils.nullSafeHashCode(getActions()) + * 27; + } + + /** + * @return the actions + */ + public LinkedList getActions() { + return actions; + } + + /** + * @param actions the actions to set + */ + public void setActions(LinkedList actions) { + this.actions = actions; + } + + /** + * @param action the action to add + */ + public void addAction(AbstractActionModel action) { + if (action == null) { + return; + } + if (actions == null) { + actions = new LinkedList(); + } + actions.add(action); + } + + /** + * @param actions the actions to add + */ + public void addAction(LinkedList actions) { + if (actions == null || actions.isEmpty()) { + return; + } + if (this.actions == null) { + this.actions = new LinkedList(); + } + this.actions.addAll(actions); + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/AttributeModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/AttributeModel.java new file mode 100644 index 00000000..e2b2491b --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/AttributeModel.java @@ -0,0 +1,160 @@ +/* + * 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.model; + +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Model support for attributes. + *

+ * A meta attribute describing or otherwise annotating it's holder. + * + * @author Scott Andrews + */ +public class AttributeModel extends AbstractModel { + private String name; + private String type; + private String value; + + /** + * Create an attribute model + * @param name the name of the attribute + * @param value the value of the attribute + */ + public AttributeModel(String name, String value) { + setName(name); + setValue(value); + } + + /** + * Create an attribute model + * @param name the name of the attribute + * @param value the value of the attribute + * @param type the type of the value + */ + public AttributeModel(String name, String value, String type) { + setName(name); + setValue(value); + setType(type); + } + + /** + * Merge properties + * @param model the attribute to merge into this attribute + */ + public void merge(Model model) { + if (isMergeableWith(model)) { + AttributeModel attribute = (AttributeModel) model; + setType(merge(getType(), attribute.getType())); + } + } + + /** + * Tests if the model is able to be merged with this attribute + * @param model the model to test + */ + public boolean isMergeableWith(Model model) { + if (model == null) { + return false; + } + if (!(model instanceof AttributeModel)) { + return false; + } + AttributeModel attribute = (AttributeModel) model; + return ObjectUtils.nullSafeEquals(getName(), attribute.getName()) + && ObjectUtils.nullSafeEquals(getValue(), attribute.getValue()); + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (!(obj instanceof AttributeModel)) { + return false; + } + AttributeModel attribute = (AttributeModel) obj; + if (attribute == null) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getName(), attribute.getName())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getType(), attribute.getType())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getValue(), attribute.getValue())) { + return false; + } else { + return super.equals(attribute); + } + } + + public int hashCode() { + return ObjectUtils.nullSafeHashCode(getName()) * 27 + ObjectUtils.nullSafeHashCode(getType()) * 27 + + ObjectUtils.nullSafeHashCode(getValue()) * 27 + super.hashCode() * 27; + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @param name the name to set + */ + public void setName(String name) { + if (StringUtils.hasText(name)) { + this.name = name; + } else { + this.name = null; + } + } + + /** + * @return the type + */ + public String getType() { + return type; + } + + /** + * @param type the type to set + */ + public void setType(String type) { + if (StringUtils.hasText(type)) { + this.type = type; + } else { + this.type = null; + } + } + + /** + * @return the value + */ + public String getValue() { + return value; + } + + /** + * @param value the value to set + */ + public void setValue(String value) { + if (StringUtils.hasText(value)) { + this.value = value; + } else { + this.value = null; + } + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/BeanImportModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/BeanImportModel.java new file mode 100644 index 00000000..8d210216 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/BeanImportModel.java @@ -0,0 +1,91 @@ +/* + * 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.model; + +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Model support for bean imports. + *

+ * Imports user-defined beans defined at a resource location. These beans become part of the flow's bean factory and are + * resolvable using flow expressions. + * + * @author Scott Andrews + */ +public class BeanImportModel extends AbstractModel { + private String resource; + + /** + * Create a bean import model + * @param resource the resource containing beans to import + */ + public BeanImportModel(String resource) { + setResource(resource); + } + + /** + * Bean imports are not mergeable + */ + public void merge(Model model) { + // not mergeable + } + + /** + * Bean imports are not mergeable + */ + public boolean isMergeableWith(Model model) { + return false; + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (!(obj instanceof BeanImportModel)) { + return false; + } + BeanImportModel beanImport = (BeanImportModel) obj; + if (beanImport == null) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getResource(), beanImport.getResource())) { + return false; + } else { + return true; + } + } + + public int hashCode() { + return ObjectUtils.nullSafeHashCode(getResource()) * 27; + } + + /** + * @return the resource + */ + public String getResource() { + return resource; + } + + /** + * @param resource the resource to set + */ + public void setResource(String resource) { + if (StringUtils.hasText(resource)) { + this.resource = resource; + } else { + this.resource = null; + } + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/DecisionStateModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/DecisionStateModel.java new file mode 100644 index 00000000..2ec82453 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/DecisionStateModel.java @@ -0,0 +1,211 @@ +/* + * 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.model; + +import java.util.LinkedList; + +import org.springframework.util.ObjectUtils; + +/** + * Model support for decision states. + *

+ * Evaluates one or more expressions to decide what state to transition to next. Intended to be used as an idempotent + * 'navigation' or 'routing' state. + *

+ * A decision state is a transitionable state. A decision state transition can be triggered by evaluating a boolean + * expression against the flow execution request context. To define transition expressions, use the 'if' element. + * + * @author Scott Andrews + */ +public class DecisionStateModel extends AbstractStateModel { + private LinkedList ifs; + private LinkedList onExitActions; + + /** + * Create a decision state model + * @param id the state identifier + */ + public DecisionStateModel(String id) { + setId(id); + } + + /** + * Create a decision state model + * @param id the state identifier + * @param ifs decision tests + * @param onExitActions actions to execute before exiting this state + * @param attributes meta attributes for this state + * @param secured security settings for this state + * @param onEntryActions actions to execute upon entering this state + * @param exceptionHandlers exception handlers for this state + */ + public DecisionStateModel(String id, LinkedList ifs, LinkedList onExitActions, LinkedList attributes, + SecuredModel secured, LinkedList onEntryActions, LinkedList exceptionHandlers) { + setId(id); + setIfs(ifs); + setOnExitActions(onExitActions); + setAttributes(attributes); + setSecured(secured); + setOnEntryActions(onEntryActions); + setExceptionHandlers(exceptionHandlers); + } + + /** + * Merge properties + * @param model the decision state to merge into this state + */ + public void merge(Model model) { + if (isMergeableWith(model)) { + DecisionStateModel state = (DecisionStateModel) model; + setAttributes(merge(getAttributes(), state.getAttributes())); + setSecured((SecuredModel) merge(getSecured(), state.getSecured())); + setOnEntryActions(merge(getOnEntryActions(), state.getOnEntryActions(), false)); + setExceptionHandlers(merge(getExceptionHandlers(), state.getExceptionHandlers())); + setIfs(merge(getIfs(), state.getIfs())); + setOnExitActions(merge(getOnExitActions(), state.getOnExitActions(), false)); + } + } + + /** + * Tests if the model is able to be merged with this decision state + * @param model the model to test + */ + public boolean isMergeableWith(Model model) { + if (model == null) { + return false; + } + if (!(model instanceof DecisionStateModel)) { + return false; + } + DecisionStateModel state = (DecisionStateModel) model; + return ObjectUtils.nullSafeEquals(getId(), state.getId()); + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (!(obj instanceof DecisionStateModel)) { + return false; + } + DecisionStateModel state = (DecisionStateModel) obj; + if (state == null) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getId(), state.getId())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getAttributes(), state.getAttributes())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getSecured(), state.getSecured())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getOnEntryActions(), state.getOnEntryActions())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getExceptionHandlers(), state.getExceptionHandlers())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getIfs(), state.getIfs())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getOnExitActions(), state.getOnExitActions())) { + return false; + } else { + return true; + } + } + + public int hashCode() { + return ObjectUtils.nullSafeHashCode(getId()) * 27 + ObjectUtils.nullSafeHashCode(getAttributes()) * 27 + + ObjectUtils.nullSafeHashCode(getSecured()) * 27 + ObjectUtils.nullSafeHashCode(getOnEntryActions()) + * 27 + ObjectUtils.nullSafeHashCode(getExceptionHandlers()) * 27 + + ObjectUtils.nullSafeHashCode(getIfs()) * 27 + ObjectUtils.nullSafeHashCode(getOnExitActions()) * 27; + } + + /** + * @return the ifs + */ + public LinkedList getIfs() { + return ifs; + } + + /** + * @param ifs the ifs to set + */ + public void setIfs(LinkedList ifs) { + this.ifs = ifs; + } + + /** + * @param conditional the if to add + */ + public void addIf(IfModel conditional) { + if (conditional == null) { + return; + } + if (ifs == null) { + ifs = new LinkedList(); + } + ifs.add(conditional); + } + + /** + * @param ifs the ifs to add + */ + public void addIf(LinkedList ifs) { + if (ifs == null || ifs.isEmpty()) { + return; + } + if (this.ifs == null) { + this.ifs = new LinkedList(); + } + this.ifs.addAll(ifs); + } + + /** + * @return the on exit actions + */ + public LinkedList getOnExitActions() { + return onExitActions; + } + + /** + * @param onExitActions the on exit actions to set + */ + public void setOnExitActions(LinkedList onExitActions) { + this.onExitActions = onExitActions; + } + + /** + * @param onExitAction the on exit action to add + */ + public void addOnExitAction(AbstractActionModel onExitAction) { + if (onExitAction == null) { + return; + } + if (onExitActions == null) { + onExitActions = new LinkedList(); + } + onExitActions.add(onExitAction); + } + + /** + * @param onExitActions the on exit actions to add + */ + public void addOnExitActions(LinkedList onExitActions) { + if (onExitActions == null || onExitActions.isEmpty()) { + return; + } + if (this.onExitActions == null) { + this.onExitActions = new LinkedList(); + } + this.onExitActions.addAll(onExitActions); + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/EndStateModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/EndStateModel.java new file mode 100644 index 00000000..830ace77 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/EndStateModel.java @@ -0,0 +1,217 @@ +/* + * 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.model; + +import java.util.LinkedList; + +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Model support for end states. + *

+ * A state that terminates this flow when entered. Defines a flow outcome. + *

+ * An end state is not transitionable; there are never transitions out of an end state. When an end-state is entered, an + * instance of this flow is terminated. + *

+ * When this flow terminates, if it was the "root" flow the entire execution is terminated. If this flow was a subflow, + * its parent flow resumes. + * + * @author Scott Andrews + */ +public class EndStateModel extends AbstractStateModel { + private String view; + private String commit; + private LinkedList outputs; + + /** + * Create an end state model + * @param id the state identifier + */ + public EndStateModel(String id) { + setId(id); + } + + /** + * Create an end state model + * @param id the state identifier + * @param view the view to render + * @param commit indicate if the persistence context should be committed + * @param outputs output mappings + * @param attributes meta attributes for the state + * @param secured security settings for the state + * @param onEntryActions actions to execute when entering the state + * @param exceptionHandlers exception handlers for the state + */ + public EndStateModel(String id, String view, String commit, LinkedList outputs, LinkedList attributes, + SecuredModel secured, LinkedList onEntryActions, LinkedList exceptionHandlers) { + setId(id); + setView(view); + setCommit(commit); + setOutputs(outputs); + setAttributes(attributes); + setSecured(secured); + setOnEntryActions(onEntryActions); + setExceptionHandlers(exceptionHandlers); + } + + /** + * Merge properties + * @param model the end state to merge into this state + */ + public void merge(Model model) { + if (isMergeableWith(model)) { + EndStateModel state = (EndStateModel) model; + setAttributes(merge(getAttributes(), state.getAttributes())); + setSecured((SecuredModel) merge(getSecured(), state.getSecured())); + setOnEntryActions(merge(getOnEntryActions(), state.getOnEntryActions(), false)); + setExceptionHandlers(merge(getExceptionHandlers(), state.getExceptionHandlers())); + setView(merge(getView(), state.getView())); + setCommit(merge(getCommit(), state.getCommit())); + setOutputs(merge(getOutputs(), state.getOutputs(), false)); + } + } + + /** + * Tests if the model is able to be merged with this end state + * @param model the model to test + */ + public boolean isMergeableWith(Model model) { + if (model == null) { + return false; + } + if (!(model instanceof EndStateModel)) { + return false; + } + EndStateModel state = (EndStateModel) model; + return ObjectUtils.nullSafeEquals(getId(), state.getId()); + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (!(obj instanceof EndStateModel)) { + return false; + } + EndStateModel state = (EndStateModel) obj; + if (state == null) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getId(), state.getId())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getAttributes(), state.getAttributes())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getSecured(), state.getSecured())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getOnEntryActions(), state.getOnEntryActions())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getExceptionHandlers(), state.getExceptionHandlers())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getView(), state.getView())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getCommit(), state.getCommit())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getOutputs(), state.getOutputs())) { + return false; + } else { + return true; + } + } + + public int hasCode() { + return ObjectUtils.nullSafeHashCode(getId()) * 27 + ObjectUtils.nullSafeHashCode(getAttributes()) * 27 + + ObjectUtils.nullSafeHashCode(getSecured()) * 27 + ObjectUtils.nullSafeHashCode(getOnEntryActions()) + * 27 + ObjectUtils.nullSafeHashCode(getExceptionHandlers()) * 27 + + ObjectUtils.nullSafeHashCode(getView()) * 27 + ObjectUtils.nullSafeHashCode(getCommit()) * 27 + + ObjectUtils.nullSafeHashCode(getOutputs()) * 27; + } + + /** + * @return the view + */ + public String getView() { + return view; + } + + /** + * @param view the view factory to set + */ + public void setView(String view) { + if (StringUtils.hasText(view)) { + this.view = view; + } else { + this.view = null; + } + } + + /** + * @return the commit + */ + public String getCommit() { + return commit; + } + + /** + * @param commit the commit to set + */ + public void setCommit(String commit) { + if (StringUtils.hasText(commit)) { + this.commit = commit; + } else { + this.commit = null; + } + } + + /** + * @return the outputs + */ + public LinkedList getOutputs() { + return outputs; + } + + /** + * @param outputs the outputs to set + */ + public void setOutputs(LinkedList outputs) { + this.outputs = outputs; + } + + /** + * @param output the output mapping to add + */ + public void addOutput(OutputModel output) { + if (output == null) { + return; + } + if (outputs == null) { + outputs = new LinkedList(); + } + outputs.add(output); + } + + /** + * @param outputs the output mappings to add + */ + public void addOutputs(LinkedList outputs) { + if (outputs == null || outputs.isEmpty()) { + return; + } + if (this.outputs == null) { + this.outputs = new LinkedList(); + } + this.outputs.addAll(outputs); + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/EvaluateModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/EvaluateModel.java new file mode 100644 index 00000000..0fabb3bf --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/EvaluateModel.java @@ -0,0 +1,168 @@ +/* + * 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.model; + +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Model support for evaluate actions. + *

+ * Evaluates an expression against the flow request context. + * + * @author Scott Andrews + */ +public class EvaluateModel extends AbstractActionModel { + private String expression; + private String result; + private String resultType; + + /** + * Create an evaluate action model + * @param expression the expression to evaluate + */ + public EvaluateModel(String expression) { + setExpression(expression); + } + + /** + * Create an evaluate action model + * @param expression the expression to evaluate + * @param result where to store the result of the expressions + */ + public EvaluateModel(String expression, String result) { + setExpression(expression); + setResult(result); + } + + /** + * Create an evaluate action model + * @param expression the expression to evaluate + * @param result where to store the result of the expressions + * @param resultType the type of the result + */ + public EvaluateModel(String expression, String result, String resultType) { + setExpression(expression); + setResult(result); + setResultType(resultType); + } + + /** + * Merge properties + * @param model the evaluate action to merge into this evaluate + */ + public void merge(Model model) { + if (isMergeableWith(model)) { + EvaluateModel evaluate = (EvaluateModel) model; + setResult(merge(getResult(), evaluate.getResult())); + setResultType(merge(getResultType(), evaluate.getResultType())); + } + } + + /** + * Tests if the model is able to be merged with this evaluate action + * @param model the model to test + */ + public boolean isMergeableWith(Model model) { + if (model == null) { + return false; + } + if (!(model instanceof EvaluateModel)) { + return false; + } + EvaluateModel evaluate = (EvaluateModel) model; + return ObjectUtils.nullSafeEquals(getExpression(), evaluate.getExpression()); + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (!(obj instanceof EvaluateModel)) { + return false; + } + EvaluateModel evaluate = (EvaluateModel) obj; + if (evaluate == null) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getExpression(), evaluate.getExpression())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getResult(), evaluate.getResult())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getResultType(), evaluate.getResultType())) { + return false; + } else { + return true; + } + } + + public int hashCode() { + return ObjectUtils.nullSafeHashCode(getExpression()) * 27 + ObjectUtils.nullSafeHashCode(getResult()) * 27 + + ObjectUtils.nullSafeHashCode(getResultType()) * 27; + } + + /** + * @return the expression + */ + public String getExpression() { + return expression; + } + + /** + * @param expression the expression to set + */ + public void setExpression(String expression) { + if (StringUtils.hasText(expression)) { + this.expression = expression; + } else { + this.expression = null; + } + } + + /** + * @return the result + */ + public String getResult() { + return result; + } + + /** + * @param result the result to set + */ + public void setResult(String result) { + if (StringUtils.hasText(result)) { + this.result = result; + } else { + this.result = null; + } + } + + /** + * @return the result type + */ + public String getResultType() { + return resultType; + } + + /** + * @param resultType the result type to set + */ + public void setResultType(String resultType) { + if (StringUtils.hasText(resultType)) { + this.resultType = resultType; + } else { + this.resultType = null; + } + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/ExceptionHandlerModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/ExceptionHandlerModel.java new file mode 100644 index 00000000..d0953646 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/ExceptionHandlerModel.java @@ -0,0 +1,87 @@ +/* + * Copyright 2004-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.webflow.engine.model; + +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Model support for exception handlers. + *

+ * Handles exceptions that occur during flow execution. Exception handlers may be attached at the state or flow level. + * + * @author Scott Andrews + */ +public class ExceptionHandlerModel extends AbstractModel { + private String beanName; + + /** + * Create an exception handler model + * @param beanName the name of the bean to handle exceptions + */ + public ExceptionHandlerModel(String beanName) { + setBeanName(beanName); + } + + /** + * Exception handlers are not mergeable + */ + public void merge(Model model) { + // not mergable + } + + public boolean isMergeableWith(Model model) { + return false; + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (!(obj instanceof ExceptionHandlerModel)) { + return false; + } + ExceptionHandlerModel exceptionHandler = (ExceptionHandlerModel) obj; + if (exceptionHandler == null) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getBeanName(), exceptionHandler.getBeanName())) { + return false; + } else { + return true; + } + } + + public int hashCode() { + return ObjectUtils.nullSafeHashCode(getBeanName()) * 27; + } + + /** + * @return the bean name + */ + public String getBeanName() { + return beanName; + } + + /** + * @param beanName the bean name to set + */ + public void setBeanName(String beanName) { + if (StringUtils.hasText(beanName)) { + this.beanName = beanName; + } else { + this.beanName = null; + } + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/FlowModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/FlowModel.java new file mode 100644 index 00000000..b4256eea --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/FlowModel.java @@ -0,0 +1,705 @@ +/* + * 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.model; + +import java.util.LinkedList; + +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Model support for flows. + *

+ * Defines exactly one flow definition. A flow is composed of one or more states that define the steps of a + * conversation. One of those steps is the start state, which defines the conversation's starting point.
+ * A flow may also exhibit the following characteristics: + *

+ * + * @author Scott Andrews + */ +public class FlowModel extends AbstractModel { + // private String id; + private String parent; + private String startStateId; + private LinkedList attributes; + private SecuredModel secured; + private PersistenceContextModel persistenceContext; + private LinkedList vars; + private LinkedList inputs; + private LinkedList outputs; + private LinkedList onStartActions; + private LinkedList states; + private LinkedList globalTransitions; + private LinkedList onEndActions; + private LinkedList exceptionHandlers; + private LinkedList beanImports; + + /** + * Create a flow model + */ + public FlowModel() { + } + + /** + * Merge properties + * @param model the flow to merge into this flow + */ + public void merge(Model model) { + if (isMergeableWith(model)) { + FlowModel flow = (FlowModel) model; + setParent(null); + setStartStateId(merge(getStartStateId(), flow.getStartStateId())); + setAttributes(merge(getAttributes(), flow.getAttributes())); + setSecured((SecuredModel) merge(getSecured(), flow.getSecured())); + setPersistenceContext((PersistenceContextModel) merge(getPersistenceContext(), flow.getPersistenceContext())); + setVars(merge(getVars(), flow.getVars(), false)); + setInputs(merge(getInputs(), flow.getInputs())); + setOutputs(merge(getOutputs(), flow.getOutputs())); + setOnStartActions(merge(getOnStartActions(), flow.getOnStartActions(), false)); + setStates(merge(getStates(), flow.getStates())); + setGlobalTransitions(merge(getGlobalTransitions(), flow.getGlobalTransitions())); + setOnEndActions(merge(getOnEndActions(), flow.getOnEndActions(), false)); + setExceptionHandlers(merge(getExceptionHandlers(), flow.getExceptionHandlers())); + setBeanImports(merge(getBeanImports(), flow.getBeanImports())); + } + } + + /** + * Tests if the model is able to be merged with this flow + * @param model the model to test + */ + public boolean isMergeableWith(Model model) { + if (model == null) { + return false; + } + if ((model instanceof FlowModel)) { + return true; + } else { + return false; + } + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (!(obj instanceof FlowModel)) { + return false; + } + FlowModel flow = (FlowModel) obj; + if (flow == null) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getParent(), flow.getParent())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getStartStateId(), flow.getStartStateId())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getAttributes(), flow.getAttributes())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getSecured(), flow.getSecured())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getPersistenceContext(), flow.getPersistenceContext())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getVars(), flow.getVars())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getInputs(), flow.getInputs())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getOutputs(), flow.getOutputs())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getOnStartActions(), flow.getOnStartActions())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getStates(), flow.getStates())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getGlobalTransitions(), flow.getGlobalTransitions())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getOnEndActions(), flow.getOnEndActions())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getExceptionHandlers(), flow.getExceptionHandlers())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getBeanImports(), flow.getBeanImports())) { + return false; + } else { + return true; + } + } + + public int hashCode() { + return ObjectUtils.nullSafeHashCode(getParent()) * 27 + ObjectUtils.nullSafeHashCode(getStartStateId()) * 27 + + ObjectUtils.nullSafeHashCode(getAttributes()) * 27 + ObjectUtils.nullSafeHashCode(getSecured()) * 27 + + ObjectUtils.nullSafeHashCode(getPersistenceContext()) * 27 + ObjectUtils.nullSafeHashCode(getVars()) + * 27 + ObjectUtils.nullSafeHashCode(getInputs()) * 27 + ObjectUtils.nullSafeHashCode(getOutputs()) * 27 + + ObjectUtils.nullSafeHashCode(getOnStartActions()) * 27 + ObjectUtils.nullSafeHashCode(getStates()) + * 27 + ObjectUtils.nullSafeHashCode(getGlobalTransitions()) * 27 + + ObjectUtils.nullSafeHashCode(getOnEndActions()) * 27 + + ObjectUtils.nullSafeHashCode(getExceptionHandlers()) * 27 + + ObjectUtils.nullSafeHashCode(getBeanImports()) * 27; + } + + /** + * @return the parent + */ + public String getParent() { + return parent; + } + + /** + * @param parent the parent to set + */ + public void setParent(String parent) { + if (StringUtils.hasText(parent)) { + this.parent = parent; + } else { + this.parent = null; + } + } + + /** + * @return the id of the flow's start state + */ + public String getStartStateId() { + return startStateId; + } + + /** + * @param startStateId the id of the flow's start state to set + */ + public void setStartStateId(String startStateId) { + if (StringUtils.hasText(startStateId)) { + this.startStateId = startStateId; + } else { + this.startStateId = null; + } + } + + /** + * @param startState the flow's start state to set + */ + public void setStartState(AbstractStateModel startState) { + setStartStateId(startState.getId()); + } + + /** + * @return the attributes + */ + public LinkedList getAttributes() { + return attributes; + } + + /** + * @param attributes the attributes to set + */ + public void setAttributes(LinkedList attributes) { + this.attributes = attributes; + } + + /** + * @param attribute the attribute to add + */ + public void addAttribute(AttributeModel attribute) { + if (attribute == null) { + return; + } + if (attributes == null) { + attributes = new LinkedList(); + } + attributes.add(attribute); + } + + /** + * @param attributes the attributes to add + */ + public void addAttributes(LinkedList attributes) { + if (attributes == null || attributes.isEmpty()) { + return; + } + if (this.attributes == null) { + this.attributes = new LinkedList(); + } + this.attributes.addAll(attributes); + } + + /** + * @return the secured + */ + public SecuredModel getSecured() { + return secured; + } + + /** + * @param secured the secured to set + */ + public void setSecured(SecuredModel secured) { + this.secured = secured; + } + + /** + * @return the persistence context + */ + public PersistenceContextModel getPersistenceContext() { + return persistenceContext; + } + + /** + * @param persistenceContext the persistence context to set + */ + public void setPersistenceContext(PersistenceContextModel persistenceContext) { + this.persistenceContext = persistenceContext; + } + + /** + * @return the vars + */ + public LinkedList getVars() { + return vars; + } + + /** + * @param vars the vars to set + */ + public void setVars(LinkedList vars) { + this.vars = vars; + } + + /** + * @param var the var to add + */ + public void addVar(VarModel var) { + if (var == null) { + return; + } + if (vars == null) { + vars = new LinkedList(); + } + vars.add(var); + } + + /** + * @param vars the vars to add + */ + public void addVars(LinkedList vars) { + if (vars == null || vars.isEmpty()) { + return; + } + if (this.vars == null) { + this.vars = new LinkedList(); + } + this.vars.addAll(vars); + } + + /** + * @return the input mappings + */ + public LinkedList getInputs() { + return inputs; + } + + /** + * @param inputs the input mappings to set + */ + public void setInputs(LinkedList inputs) { + this.inputs = inputs; + } + + /** + * @param input the input mapping to add + */ + public void addInput(InputModel input) { + if (input == null) { + return; + } + if (inputs == null) { + inputs = new LinkedList(); + } + inputs.add(input); + } + + /** + * @param inputs the input mappings to add + */ + public void addInputs(LinkedList inputs) { + if (inputs == null || inputs.isEmpty()) { + return; + } + if (this.inputs == null) { + this.inputs = new LinkedList(); + } + this.inputs.addAll(inputs); + } + + /** + * @return the output mappings + */ + public LinkedList getOutputs() { + return outputs; + } + + /** + * @param outputs the output mappings to set + */ + public void setOutputs(LinkedList outputs) { + this.outputs = outputs; + } + + /** + * @param output the output mapping to add + */ + public void addOutput(OutputModel output) { + if (output == null) { + return; + } + if (outputs == null) { + outputs = new LinkedList(); + } + outputs.add(output); + } + + /** + * @param outputs the output mappings to add + */ + public void addOutputs(LinkedList outputs) { + if (outputs == null || outputs.isEmpty()) { + return; + } + if (this.outputs == null) { + this.outputs = new LinkedList(); + } + this.outputs.addAll(outputs); + } + + /** + * @return the on start actions + */ + public LinkedList getOnStartActions() { + return onStartActions; + } + + /** + * @param onStartActions the on start actions to set + */ + public void setOnStartActions(LinkedList onStartActions) { + this.onStartActions = onStartActions; + } + + /** + * @param onStartAction the on start action to add + */ + public void addOnStartAction(AbstractActionModel onStartAction) { + if (onStartAction == null) { + return; + } + if (onStartActions == null) { + onStartActions = new LinkedList(); + } + onStartActions.add(onStartAction); + } + + /** + * @param onStartActions the on start actions to add + */ + public void addOnStartActions(LinkedList onStartActions) { + if (onStartActions == null || onStartActions.isEmpty()) { + return; + } + if (this.onStartActions == null) { + this.onStartActions = new LinkedList(); + } + this.onStartActions.addAll(onStartActions); + } + + /** + * @return the states + */ + public LinkedList getStates() { + return states; + } + + /** + * @param states the states to set + */ + public void setStates(LinkedList states) { + this.states = states; + } + + /** + * @param state the state to add + */ + public void addState(AbstractStateModel state) { + if (state == null) { + return; + } + if (states == null) { + states = new LinkedList(); + } + states.add(state); + } + + /** + * @param states the states to add + */ + public void addStates(LinkedList states) { + if (states == null || states.isEmpty()) { + return; + } + if (this.states == null) { + this.states = new LinkedList(); + } + this.states.addAll(states); + } + + /** + * @param state the action state to add + */ + public void addActionState(ActionStateModel state) { + addState(state); + } + + /** + * @param states the action states to add + */ + public void addActionStates(LinkedList states) { + addStates(states); + } + + /** + * @param state the view state to add + */ + public void addViewState(ViewStateModel state) { + addState(state); + } + + /** + * @param states the view states to add + */ + public void addViewStates(LinkedList states) { + addStates(states); + } + + /** + * @param state the decision state to add + */ + public void addDecisionState(DecisionStateModel state) { + addState(state); + } + + /** + * @param states the decision states to add + */ + public void addDecisionStates(LinkedList states) { + addStates(states); + } + + /** + * @param state the subflow state to add + */ + public void addSubflowState(SubflowStateModel state) { + addState(state); + } + + /** + * @param states the subflow states to add + */ + public void addSubflowStates(LinkedList states) { + addStates(states); + } + + /** + * @param state the end state to add + */ + public void addEndState(EndStateModel state) { + addState(state); + } + + /** + * @param states the end states to add + */ + public void addEndStates(LinkedList states) { + addStates(states); + } + + /** + * @return the global transitions + */ + public LinkedList getGlobalTransitions() { + return globalTransitions; + } + + /** + * @param globalTransitions the global transitions to set + */ + public void setGlobalTransitions(LinkedList globalTransitions) { + this.globalTransitions = globalTransitions; + } + + /** + * @param globalTransition the global transition to add + */ + public void addGlobalTransition(TransitionModel globalTransition) { + if (globalTransition == null) { + return; + } + if (globalTransitions == null) { + globalTransitions = new LinkedList(); + } + globalTransitions.add(globalTransition); + } + + /** + * @param globalTransitions the global transitions to add + */ + public void addGlobalTransitions(LinkedList globalTransitions) { + if (globalTransitions == null || globalTransitions.isEmpty()) { + return; + } + if (this.globalTransitions == null) { + this.globalTransitions = new LinkedList(); + } + this.globalTransitions.addAll(globalTransitions); + } + + /** + * @return the on end actions + */ + public LinkedList getOnEndActions() { + return onEndActions; + } + + /** + * @param onEndActions the on end actions to set + */ + public void setOnEndActions(LinkedList onEndActions) { + this.onEndActions = onEndActions; + } + + /** + * @param onEndAction the on end action to add + */ + public void addOnEndAction(AbstractActionModel onEndAction) { + if (onEndAction == null) { + return; + } + if (onEndActions == null) { + onEndActions = new LinkedList(); + } + onEndActions.add(onEndAction); + } + + /** + * @param onEndActions the on end actions to add + */ + public void addOnEndActions(LinkedList onEndActions) { + if (onEndActions == null || onEndActions.isEmpty()) { + return; + } + if (this.onEndActions == null) { + this.onEndActions = new LinkedList(); + } + this.onEndActions.addAll(onEndActions); + } + + /** + * @return the exception handlers + */ + public LinkedList getExceptionHandlers() { + return exceptionHandlers; + } + + /** + * @param exceptionHandlers the exception handlers to set + */ + public void setExceptionHandlers(LinkedList exceptionHandlers) { + this.exceptionHandlers = exceptionHandlers; + } + + /** + * @param exceptionHandler the exception handler to add + */ + public void addExceptionHandler(ExceptionHandlerModel exceptionHandler) { + if (exceptionHandler == null) { + return; + } + if (exceptionHandlers == null) { + exceptionHandlers = new LinkedList(); + } + exceptionHandlers.add(exceptionHandler); + } + + /** + * @param exceptionHandlers the exception handlers to add + */ + public void addExceptionHandlers(LinkedList exceptionHandlers) { + if (exceptionHandlers == null || exceptionHandlers.isEmpty()) { + return; + } + if (this.exceptionHandlers == null) { + this.exceptionHandlers = new LinkedList(); + } + this.exceptionHandlers.addAll(exceptionHandlers); + } + + /** + * @return the bean imports + */ + public LinkedList getBeanImports() { + return beanImports; + } + + /** + * @param beanImports the bean imports to set + */ + public void setBeanImports(LinkedList beanImports) { + this.beanImports = beanImports; + } + + /** + * @param beanImport the bean import to add + */ + public void addBeanImport(BeanImportModel beanImport) { + if (beanImport == null) { + return; + } + if (beanImports == null) { + beanImports = new LinkedList(); + } + beanImports.add(beanImport); + } + + /** + * @param beanImports the bean imports to add + */ + public void addBeanImports(LinkedList beanImports) { + if (beanImports == null || beanImports.isEmpty()) { + return; + } + if (this.beanImports == null) { + this.beanImports = new LinkedList(); + } + this.beanImports.addAll(beanImports); + } + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/IfModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/IfModel.java new file mode 100644 index 00000000..f16e4041 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/IfModel.java @@ -0,0 +1,162 @@ +/* + * 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.model; + +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Model support for if elements. + *

+ * Defines a boolean expression to evaluate a target state to transition to if that expression evaluates to true. + * Optionally, this element may define an 'else' attribute to define a state to transition to if the expression + * evaluates to false. + * + * @author Scott Andrews + */ +public class IfModel extends AbstractModel { + private String test; + private String then; + private String elze; + + /** + * Create an if model + * @param test the boolean condition to test + * @param then the state to transition to if the boolean expression evaluates to true + */ + public IfModel(String test, String then) { + setTest(test); + setThen(then); + } + + /** + * Create an if model + * @param test the boolean condition to test + * @param then the state to transition to if the boolean expression evaluates to true + * @param elze the state to transition to if the boolean expression evaluates to false + */ + public IfModel(String test, String then, String elze) { + setTest(test); + setThen(then); + setElse(elze); + } + + /** + * Merge properties + * @param model the conditional to merge into this conditional + */ + public void merge(Model model) { + if (isMergeableWith(model)) { + IfModel conditional = (IfModel) model; + setThen(merge(getThen(), conditional.getThen())); + setElse(merge(getElse(), conditional.getElse())); + } + } + + /** + * Tests if the model is able to be merged with this if action + * @param model the model to test + */ + public boolean isMergeableWith(Model model) { + if (model == null) { + return false; + } + if (!(model instanceof IfModel)) { + return false; + } + IfModel conditional = (IfModel) model; + return ObjectUtils.nullSafeEquals(getTest(), conditional.getTest()); + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (!(obj instanceof IfModel)) { + return false; + } + IfModel conditional = (IfModel) obj; + if (conditional == null) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getTest(), conditional.getTest())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getThen(), conditional.getThen())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getElse(), conditional.getElse())) { + return false; + } else { + return true; + } + } + + public int hashCode() { + return ObjectUtils.nullSafeHashCode(getTest()) * 27 + ObjectUtils.nullSafeHashCode(getThen()) * 27 + + ObjectUtils.nullSafeHashCode(getElse()) * 27; + } + + /** + * @return the test + */ + public String getTest() { + return test; + } + + /** + * @param test the test to set + */ + public void setTest(String test) { + if (StringUtils.hasText(test)) { + this.test = test; + } else { + this.test = null; + } + } + + /** + * @return the then + */ + public String getThen() { + return then; + } + + /** + * @param then the then to set + */ + public void setThen(String then) { + if (StringUtils.hasText(then)) { + this.then = then; + } else { + this.then = null; + } + } + + /** + * @return the else + */ + public String getElse() { + return elze; + } + + /** + * @param elze the else to set + */ + public void setElse(String elze) { + if (StringUtils.hasText(elze)) { + this.elze = elze; + } else { + this.elze = null; + } + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/InputModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/InputModel.java new file mode 100644 index 00000000..b6677fa9 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/InputModel.java @@ -0,0 +1,108 @@ +/* + * 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.model; + +import org.springframework.util.ObjectUtils; + +/** + * Model support for input mappings. + *

+ * Maps a single input attribute into this flow or subflow. + * + * @author Scott Andrews + */ +public class InputModel extends AbstractMappingModel { + + /** + * Create an input mapping model + * @param name the name of the mapping variable + * @param value the value to map + */ + public InputModel(String name, String value) { + setName(name); + setValue(value); + } + + /** + * Create an input mapping model + * @param name the name of the mapping variable + * @param value the value to map + * @param type the type of the value + * @param required indicates if this mapping is required + */ + public InputModel(String name, String value, String type, String required) { + setName(name); + setValue(value); + setType(type); + setRequired(required); + } + + /** + * Merge properties + * @param model the mapping to merge into this mapping + */ + public void merge(Model model) { + if (isMergeableWith(model)) { + InputModel input = (InputModel) model; + setValue(merge(getValue(), input.getValue())); + setType(merge(getType(), input.getType())); + setRequired(merge(getRequired(), input.getRequired())); + } + } + + /** + * Tests if the model is able to be merged with this input mapping + * @param model the model to test + */ + public boolean isMergeableWith(Model model) { + if (model == null) { + return false; + } + if (!(model instanceof InputModel)) { + return false; + } + InputModel input = (InputModel) model; + return ObjectUtils.nullSafeEquals(getName(), input.getName()); + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (!(obj instanceof InputModel)) { + return false; + } + InputModel input = (InputModel) obj; + if (input == null) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getName(), input.getName())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getValue(), input.getValue())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getType(), input.getType())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getRequired(), input.getRequired())) { + return false; + } else { + return true; + } + } + + public int hashCode() { + return ObjectUtils.nullSafeHashCode(getName()) * 27 + ObjectUtils.nullSafeHashCode(getValue()) * 27 + + ObjectUtils.nullSafeHashCode(getType()) * 27 + ObjectUtils.nullSafeHashCode(getRequired()) * 27; + } + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/Model.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/Model.java new file mode 100644 index 00000000..c040b256 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/Model.java @@ -0,0 +1,23 @@ +package org.springframework.webflow.engine.model; + +/** + * Interface defining models. All models must be able to handle merging of their content with an eligible model. + * + * @author Scott Andrews + */ +public interface Model { + + /** + * Determine if the model is able to be merged into the current model + * @param model the model to compare + * @return true if able to merge + */ + public boolean isMergeableWith(Model model); + + /** + * Merge the model into the current model + * @param model the model to merge with + */ + public void merge(Model model); + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/OutputModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/OutputModel.java new file mode 100644 index 00000000..470f87e2 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/OutputModel.java @@ -0,0 +1,108 @@ +/* + * 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.model; + +import org.springframework.util.ObjectUtils; + +/** + * Model support for output mappings. + *

+ * Maps a single output attribute out of this flow or subflow. + * + * @author Scott Andrews + */ +public class OutputModel extends AbstractMappingModel { + + /** + * Create an output mapping model + * @param name the name of the mapping variable + * @param value the value to map + */ + public OutputModel(String name, String value) { + setName(name); + setValue(value); + } + + /** + * Create an output mapping model + * @param name the name of the mapping variable + * @param value the value to map + * @param type the type of the value + * @param required indicates if this mapping is required + */ + public OutputModel(String name, String value, String type, String required) { + setName(name); + setValue(value); + setType(type); + setRequired(required); + } + + /** + * Merge properties + * @param model the mapping to merge into this mapping + */ + public void merge(Model model) { + if (isMergeableWith(model)) { + OutputModel output = (OutputModel) model; + setValue(merge(getValue(), output.getValue())); + setType(merge(getType(), output.getType())); + setRequired(merge(getRequired(), output.getRequired())); + } + } + + /** + * Tests if the model is able to be merged with this output mapping + * @param model the model to test + */ + public boolean isMergeableWith(Model model) { + if (model == null) { + return false; + } + if (!(model instanceof OutputModel)) { + return false; + } + OutputModel output = (OutputModel) model; + return ObjectUtils.nullSafeEquals(getName(), output.getName()); + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (!(obj instanceof InputModel)) { + return false; + } + OutputModel output = (OutputModel) obj; + if (output == null) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getName(), output.getName())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getValue(), output.getValue())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getType(), output.getType())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getRequired(), output.getRequired())) { + return false; + } else { + return true; + } + } + + public int hashCode() { + return ObjectUtils.nullSafeHashCode(getName()) * 27 + ObjectUtils.nullSafeHashCode(getValue()) * 27 + + ObjectUtils.nullSafeHashCode(getType()) * 27 + ObjectUtils.nullSafeHashCode(getRequired()) * 27; + } + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/PersistenceContextModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/PersistenceContextModel.java new file mode 100644 index 00000000..5f19ebd0 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/PersistenceContextModel.java @@ -0,0 +1,51 @@ +/* + * 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.model; + +/** + * Model support for persistence context elements. + *

+ * Allocates a persistence context when this flow starts. The persistence context is closed when the flow ends. If the + * flow ends by reaching a "commit" end-state, changes made to managed persistent entities during the course of flow + * execution are flushed to the database in a transaction. + *

+ * The persistence context can be referenced from within this flow by the "entityManager" variable. + * + * @author Scott Andrews + */ +public class PersistenceContextModel extends AbstractModel { + + /** + * Create a persistence context model + */ + public PersistenceContextModel() { + } + + /** + * Persistence contexts are not mergeable + */ + public void merge(Model model) { + // not mergeable + } + + /** + * Persistence contexts are not mergeable + */ + public boolean isMergeableWith(Model model) { + return false; + } + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/RenderModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/RenderModel.java new file mode 100644 index 00000000..9f62a33d --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/RenderModel.java @@ -0,0 +1,93 @@ +/* + * 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.model; + +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Model support for render actions. + *

+ * Requests that the next view render a fragment of content. Multiple fragments may be specified using a comma + * delimiter. + * + * @author Scott Andrews + */ +public class RenderModel extends AbstractActionModel { + private String fragments; + + /** + * Create a render action model + * @param fragments the fragments to render + */ + public RenderModel(String fragments) { + setFragments(fragments); + } + + /** + * Render action models are not mergeable + * @param model the render action to merge into this render + */ + public void merge(Model model) { + // not mergeable + } + + /** + * Render action models are not mergeable + * @param model the model to test + */ + public boolean isMergeableWith(Model model) { + return false; + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (!(obj instanceof RenderModel)) { + return false; + } + RenderModel render = (RenderModel) obj; + if (render == null) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getFragments(), render.getFragments())) { + return false; + } else { + return true; + } + } + + public int hashCode() { + return ObjectUtils.nullSafeHashCode(getFragments()) * 27; + } + + /** + * @return the fragments + */ + public String getFragments() { + return fragments; + } + + /** + * @param fragments the fragments to set + */ + public void setFragments(String fragments) { + if (StringUtils.hasText(fragments)) { + this.fragments = fragments; + } else { + this.fragments = null; + } + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/SecuredModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/SecuredModel.java new file mode 100644 index 00000000..c2439c05 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/SecuredModel.java @@ -0,0 +1,139 @@ +/* + * 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.model; + +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; +import org.springframework.webflow.security.SecurityFlowExecutionListener; + +/** + * Model support for secured elements. + *

+ * Secures a flow, state or transition. The user invoking this element must meet the required attributes otherwise + * access will be denied. + *

+ * Warning: This model will only configure a security attribute in the definition. The flow execution must also + * be secured with a SecurityFlowExecutionListener. + * + * @see SecurityFlowExecutionListener + * @author Scott Andrews + */ +public class SecuredModel extends AbstractModel { + private String attributes; + private String match; + + /** + * Create a security settings model + * @param attributes the security attributes + */ + public SecuredModel(String attributes) { + setAttributes(attributes); + } + + /** + * Create a security settings model + * @param attributes the security attributes + * @param match the type of matching for the attributes + */ + public SecuredModel(String attributes, String match) { + setAttributes(attributes); + setMatch(match); + } + + /** + * Merge properties + * @param model the secured to merge into this secured + */ + public void merge(Model model) { + if (isMergeableWith(model)) { + SecuredModel secured = (SecuredModel) model; + setMatch(merge(getMatch(), secured.getMatch())); + } + } + + /** + * Tests if the model is able to be merged with this secured attribute + * @param model the model to test + */ + public boolean isMergeableWith(Model model) { + if (model == null) { + return false; + } + if (!(model instanceof SecuredModel)) { + return false; + } + SecuredModel secured = (SecuredModel) model; + return ObjectUtils.nullSafeEquals(getAttributes(), secured.getAttributes()); + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (!(obj instanceof SecuredModel)) { + return false; + } + SecuredModel secured = (SecuredModel) obj; + if (secured == null) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getAttributes(), secured.getAttributes())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getMatch(), secured.getMatch())) { + return false; + } else { + return true; + } + } + + public int hashCode() { + return ObjectUtils.nullSafeHashCode(getAttributes()) * 27 + ObjectUtils.nullSafeHashCode(getMatch()) * 27; + } + + /** + * @return the attributes + */ + public String getAttributes() { + return attributes; + } + + /** + * @param attributes the attributes to set + */ + public void setAttributes(String attributes) { + if (StringUtils.hasText(attributes)) { + this.attributes = attributes; + } else { + this.attributes = null; + } + } + + /** + * @return the match + */ + public String getMatch() { + return match; + } + + /** + * @param match the match to set + */ + public void setMatch(String match) { + if (StringUtils.hasText(match)) { + this.match = match; + } else { + this.match = null; + } + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/SetModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/SetModel.java new file mode 100644 index 00000000..a60a790a --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/SetModel.java @@ -0,0 +1,161 @@ +/* + * 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.model; + +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Model support for set actions. + *

+ * Sets an attribute value in a scope. + * + * @author Scott Andrews + */ +public class SetModel extends AbstractActionModel { + private String name; + private String value; + private String type; + + /** + * Create a set action model + * @param name the name of the property to set + * @param value the value to set + */ + public SetModel(String name, String value) { + setName(name); + setValue(value); + } + + /** + * Create a set action model + * @param name the name of the property to set + * @param value the value to set + * @param type the type of the property + */ + public SetModel(String name, String value, String type) { + setName(name); + setValue(value); + setType(type); + } + + /** + * Merge properties + * @param model the set action to merge into this set + */ + public void merge(Model model) { + if (isMergeableWith(model)) { + SetModel set = (SetModel) model; + setValue(merge(getValue(), set.getValue())); + setType(merge(getType(), set.getType())); + } + } + + /** + * Tests if the model is able to be merged with this set action + * @param model the model to test + */ + public boolean isMergeableWith(Model model) { + if (model == null) { + return false; + } + if (!(model instanceof SetModel)) { + return false; + } + SetModel set = (SetModel) model; + return ObjectUtils.nullSafeEquals(getName(), set.getName()); + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof SetModel)) { + return false; + } + SetModel set = (SetModel) obj; + if (set == null) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getName(), set.getName())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getValue(), set.getValue())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getType(), set.getType())) { + return false; + } else { + return true; + } + } + + public int hashCode() { + return ObjectUtils.nullSafeHashCode(getName()) * 27 + ObjectUtils.nullSafeHashCode(getValue()) * 27 + + ObjectUtils.nullSafeHashCode(getType()) * 27; + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @param name the name to set + */ + public void setName(String name) { + if (StringUtils.hasText(name)) { + this.name = name; + } else { + this.name = null; + } + } + + /** + * @return the value + */ + public String getValue() { + return value; + } + + /** + * @param value the value to set + */ + public void setValue(String value) { + if (StringUtils.hasText(value)) { + this.value = value; + } else { + this.value = null; + } + } + + /** + * @return the type + */ + public String getType() { + return type; + } + + /** + * @param type the type to set + */ + public void setType(String type) { + if (StringUtils.hasText(type)) { + this.type = type; + } else { + this.type = null; + } + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/SubflowStateModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/SubflowStateModel.java new file mode 100644 index 00000000..6e07b57b --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/SubflowStateModel.java @@ -0,0 +1,275 @@ +/* + * 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.model; + +import java.util.LinkedList; + +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Model support for subflow states. + *

+ * Starts another flow as a subflow when entered. When the subflow ends, this state is expected to respond to its result + * by executing a transition. + *

+ * A subflow state is a transitionable state. A transition is triggered by the subflow outcome that was reached. + * + * @author Scott Andrews + */ +public class SubflowStateModel extends AbstractTransitionableStateModel { + private String subflow; + private String subflowAttributeMapper; + private LinkedList inputs; + private LinkedList outputs; + + /** + * Create a subflow state model + * @param id the identifier of the state + * @param subflow the identifier of the flow to launch as a subflow + */ + public SubflowStateModel(String id, String subflow) { + setId(id); + setSubflow(subflow); + } + + /** + * Create a subflow state model + * @param id the identifier of the state + * @param subflow the identifier of the flow to launch as a subflow + * @param subflowAttributeMapper bean name of the attribute mapping + * @param inputs input mappings + * @param outputs output mappings + * @param attributes meta attributes for the state + * @param secured security settings for the state + * @param onEntryActions actions to be executed when entering the state + * @param exceptionHandlers exception handlers for the state + * @param transitions transitions for the state + * @param onExitActions actions to be executed before leaving the state. + */ + public SubflowStateModel(String id, String subflow, String subflowAttributeMapper, LinkedList inputs, + LinkedList outputs, LinkedList attributes, SecuredModel secured, LinkedList onEntryActions, + LinkedList exceptionHandlers, LinkedList transitions, LinkedList onExitActions) { + setId(id); + setSubflow(subflow); + setSubflowAttributeMapper(subflowAttributeMapper); + setInputs(inputs); + setOutputs(outputs); + setAttributes(attributes); + setSecured(secured); + setOnEntryActions(onEntryActions); + setExceptionHandlers(exceptionHandlers); + setTransitions(transitions); + setOnExitActions(onExitActions); + } + + /** + * Merge properties + * @param model the subflow state to merge into this state + */ + public void merge(Model model) { + if (isMergeableWith(model)) { + SubflowStateModel state = (SubflowStateModel) model; + setAttributes(merge(getAttributes(), state.getAttributes())); + setSecured((SecuredModel) merge(getSecured(), state.getSecured())); + setOnEntryActions(merge(getOnEntryActions(), state.getOnEntryActions(), false)); + setExceptionHandlers(merge(getExceptionHandlers(), state.getExceptionHandlers())); + setTransitions(merge(getTransitions(), state.getTransitions())); + setOnExitActions(merge(getOnExitActions(), state.getOnExitActions(), false)); + setSubflow(merge(getSubflow(), state.getSubflow())); + setSubflowAttributeMapper(merge(getSubflowAttributeMapper(), state.getSubflowAttributeMapper())); + setInputs(merge(getInputs(), state.getInputs())); + setOutputs(merge(getOutputs(), state.getOutputs())); + } + } + + /** + * Tests if the model is able to be merged with this subflow state + * @param model the model to test + */ + public boolean isMergeableWith(Model model) { + if (model == null) { + return false; + } + if (!(model instanceof SubflowStateModel)) { + return false; + } + SubflowStateModel state = (SubflowStateModel) model; + return ObjectUtils.nullSafeEquals(getId(), state.getId()); + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (!(obj instanceof SubflowStateModel)) { + return false; + } + SubflowStateModel state = (SubflowStateModel) obj; + if (state == null) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getId(), state.getId())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getAttributes(), state.getAttributes())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getSecured(), state.getSecured())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getOnEntryActions(), state.getOnEntryActions())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getExceptionHandlers(), state.getExceptionHandlers())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getTransitions(), state.getTransitions())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getOnExitActions(), state.getOnExitActions())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getSubflow(), state.getSubflow())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getSubflowAttributeMapper(), state.getSubflowAttributeMapper())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getInputs(), state.getInputs())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getOutputs(), state.getOutputs())) { + return false; + } else { + return true; + } + } + + public int hashCode() { + return ObjectUtils.nullSafeHashCode(getId()) * 27 + ObjectUtils.nullSafeHashCode(getAttributes()) * 27 + + ObjectUtils.nullSafeHashCode(getSecured()) * 27 + ObjectUtils.nullSafeHashCode(getOnEntryActions()) + * 27 + ObjectUtils.nullSafeHashCode(getExceptionHandlers()) * 27 + + ObjectUtils.nullSafeHashCode(getTransitions()) * 27 + + ObjectUtils.nullSafeHashCode(getOnExitActions()) * 27 + ObjectUtils.nullSafeHashCode(getSubflow()) + * 27 + ObjectUtils.nullSafeHashCode(getSubflowAttributeMapper()) * 27 + + ObjectUtils.nullSafeHashCode(getInputs()) * 27 + ObjectUtils.nullSafeHashCode(getOutputs()) * 27; + } + + /** + * @return the subflow + */ + public String getSubflow() { + return subflow; + } + + /** + * @param subflow the subflow to set + */ + public void setSubflow(String subflow) { + if (StringUtils.hasText(subflow)) { + this.subflow = subflow; + } else { + this.subflow = null; + } + } + + /** + * @return the subflow attribute mapper + */ + public String getSubflowAttributeMapper() { + return subflowAttributeMapper; + } + + /** + * @param subflowAttributeMapper the subflow attribute mapper to set + */ + public void setSubflowAttributeMapper(String subflowAttributeMapper) { + if (StringUtils.hasText(subflowAttributeMapper)) { + this.subflowAttributeMapper = subflowAttributeMapper; + } else { + this.subflowAttributeMapper = null; + } + } + + /** + * @return the input mappings + */ + public LinkedList getInputs() { + return inputs; + } + + /** + * @param inputs the input mappings to set + */ + public void setInputs(LinkedList inputs) { + this.inputs = inputs; + } + + /** + * @param input the input mapping to add + */ + public void addInput(InputModel input) { + if (input == null) { + return; + } + if (inputs == null) { + inputs = new LinkedList(); + } + inputs.add(input); + } + + /** + * @param inputs the input mappings to add + */ + public void addInputs(LinkedList inputs) { + if (inputs == null || inputs.isEmpty()) { + return; + } + if (this.inputs == null) { + this.inputs = new LinkedList(); + } + this.inputs.addAll(inputs); + } + + /** + * @return the output mappings + */ + public LinkedList getOutputs() { + return outputs; + } + + /** + * @param outputs the output mappings to set + */ + public void setOutputs(LinkedList outputs) { + this.outputs = outputs; + } + + /** + * @param output the output mapping to add + */ + public void addOutput(OutputModel output) { + if (output == null) { + return; + } + if (outputs == null) { + outputs = new LinkedList(); + } + outputs.add(output); + } + + /** + * @param outputs the output mappings to add + */ + public void addOutputs(LinkedList outputs) { + if (outputs == null || outputs.isEmpty()) { + return; + } + if (this.outputs == null) { + this.outputs = new LinkedList(); + } + this.outputs.addAll(outputs); + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/TransitionModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/TransitionModel.java new file mode 100644 index 00000000..cdf93893 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/TransitionModel.java @@ -0,0 +1,311 @@ +/* + * 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.model; + +import java.util.LinkedList; + +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Model support for transitions. + *

+ * A path from this state to another state triggered by an event. Transitions may execute one or more actions. All + * transition actions must execute successfully for the transition itself to complete. If no transition target is + * specified, the transition acts as a simple event handler and does not change the state of the flow. + * + * @author Scott Andrews + */ +public class TransitionModel extends AbstractModel { + private String on; + private String onException; + private String to; + private String bind; + private LinkedList attributes; + private SecuredModel secured; + private LinkedList actions; + + /** + * Create a transition model + * @param on the matching criteria + */ + public TransitionModel(String on) { + setOn(on); + } + + /** + * Create a transition model + * @param on the matching criteria + * @param to the identifier of the state to target + */ + public TransitionModel(String on, String to) { + setOn(on); + setTo(to); + } + + /** + * Create a transition model + * @param on the matching criteria + * @param to the identifier of the state to target + * @param onException class name of the exception to handle + * @param bind if the transition should bind to the defined model. Valid only for view state transitions + * @param attributes meta attributes for the transition + * @param secured security settings for the transition + * @param actions actions to be executed after matching the transition + */ + public TransitionModel(String on, String to, String onException, String bind, LinkedList attributes, + SecuredModel secured, LinkedList actions) { + setOn(on); + setTo(to); + setOnException(onException); + setBind(bind); + setAttributes(attributes); + setSecured(secured); + setActions(actions); + } + + /** + * Merge properties + * @param model the transition to merge into this transition + */ + public void merge(Model model) { + if (isMergeableWith(model)) { + TransitionModel transition = (TransitionModel) model; + setOnException(merge(getOnException(), transition.getOnException())); + setTo(merge(getTo(), transition.getTo())); + setBind(merge(getBind(), transition.getBind())); + setAttributes(merge(getAttributes(), transition.getAttributes())); + setSecured((SecuredModel) merge(getSecured(), transition.getSecured())); + setActions(merge(getActions(), transition.getActions(), false)); + } + } + + /** + * Tests if the model is able to be merged with this transition + * @param model the model to test + */ + public boolean isMergeableWith(Model model) { + if (model == null) { + return false; + } + if (!(model instanceof TransitionModel)) { + return false; + } + TransitionModel transition = (TransitionModel) model; + return ObjectUtils.nullSafeEquals(getOn(), transition.getOn()); + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (!(obj instanceof TransitionModel)) { + return false; + } + TransitionModel transition = (TransitionModel) obj; + if (transition == null) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getOn(), transition.getOn())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getOnException(), transition.getOnException())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getTo(), transition.getTo())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getBind(), transition.getBind())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getAttributes(), transition.getAttributes())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getSecured(), transition.getSecured())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getActions(), transition.getActions())) { + return false; + } else { + return true; + } + } + + public int hashCode() { + return ObjectUtils.nullSafeHashCode(getOn()) * 27 + ObjectUtils.nullSafeHashCode(getOnException()) * 27 + + ObjectUtils.nullSafeHashCode(getTo()) * 27 + ObjectUtils.nullSafeHashCode(getBind()) * 27 + + ObjectUtils.nullSafeHashCode(getAttributes()) * 27 + ObjectUtils.nullSafeHashCode(getSecured()) * 27 + + ObjectUtils.nullSafeHashCode(getActions()) * 27; + } + + /** + * @return the on + */ + public String getOn() { + return on; + } + + /** + * @param on the on to set + */ + public void setOn(String on) { + if (StringUtils.hasText(on)) { + this.on = on; + } else { + this.on = null; + } + } + + /** + * @return the on exception + */ + public String getOnException() { + return onException; + } + + /** + * @param onException the on exception to set + */ + public void setOnException(String onException) { + if (StringUtils.hasText(onException)) { + this.onException = onException; + } else { + this.onException = null; + } + } + + /** + * @return the to + */ + public String getTo() { + return to; + } + + /** + * @param to the to to set + */ + public void setTo(String to) { + if (StringUtils.hasText(to)) { + this.to = to; + } else { + this.to = null; + } + } + + /** + * @return the bind + */ + public String getBind() { + return bind; + } + + /** + * @param bind the bind to set + */ + public void setBind(String bind) { + if (StringUtils.hasText(bind)) { + this.bind = bind; + } else { + this.bind = null; + } + } + + /** + * @return the attributes + */ + public LinkedList getAttributes() { + return attributes; + } + + /** + * @param attributes the attributes to set + */ + public void setAttributes(LinkedList attributes) { + this.attributes = attributes; + } + + /** + * @param attribute the attribute to add + */ + public void addAttribute(AttributeModel attribute) { + if (attribute == null) { + return; + } + if (attributes == null) { + attributes = new LinkedList(); + } + attributes.add(attribute); + } + + /** + * @param attributes the attributes to add + */ + public void addAttributes(LinkedList attributes) { + if (attributes == null || attributes.isEmpty()) { + return; + } + if (this.attributes == null) { + this.attributes = new LinkedList(); + } + this.attributes.addAll(attributes); + } + + /** + * @return the secured + */ + public SecuredModel getSecured() { + return secured; + } + + /** + * @param secured the secured to set + */ + public void setSecured(SecuredModel secured) { + this.secured = secured; + } + + /** + * @return the actions + */ + public LinkedList getActions() { + return actions; + } + + /** + * @param actions the actions to set + */ + public void setActions(LinkedList actions) { + this.actions = actions; + } + + /** + * @param action the action to add + */ + public void addAction(AbstractActionModel action) { + if (action == null) { + return; + } + if (actions == null) { + actions = new LinkedList(); + } + actions.add(action); + } + + /** + * @param actions the actions to add + */ + public void addActions(LinkedList actions) { + if (actions == null || actions.isEmpty()) { + return; + } + if (this.actions == null) { + this.actions = new LinkedList(); + } + this.actions.addAll(actions); + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/VarModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/VarModel.java new file mode 100644 index 00000000..8ebd8e6c --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/VarModel.java @@ -0,0 +1,161 @@ +/* + * 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.model; + +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Model support for var elements. + *

+ * An instance variable. Variables are created when the flow starts or state enters and destroyed when the flow or state + * ends, respectively. + * + * @author Scott Andrews + */ +public class VarModel extends AbstractModel { + private String name; + private String className; + private String scope; + + /** + * Create a variable model + * @param name the name of the variable + * @param className the class type of the variable + */ + public VarModel(String name, String className) { + setName(name); + setClassName(className); + } + + /** + * Create a variable model + * @param name the name of the variable + * @param className the class type of the variable + * @param scope the scope to store the variable + */ + public VarModel(String name, String className, String scope) { + setName(name); + setClassName(className); + setScope(scope); + } + + /** + * Merge properties + * @param model the var to merge into this var + */ + public void merge(Model model) { + if (isMergeableWith(model)) { + VarModel var = (VarModel) model; + setClassName(merge(getClassName(), var.getClassName())); + setScope(merge(getScope(), var.getScope())); + } + } + + /** + * Tests if the model is able to be merged with this var + * @param model the model to test + */ + public boolean isMergeableWith(Model model) { + if (model == null) { + return false; + } + if (!(model instanceof VarModel)) { + return false; + } + VarModel var = (VarModel) model; + return ObjectUtils.nullSafeEquals(getName(), var.getName()); + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (!(obj instanceof VarModel)) { + return false; + } + VarModel var = (VarModel) obj; + if (var == null) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getName(), var.getName())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getClassName(), var.getClassName())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getScope(), var.getScope())) { + return false; + } else { + return true; + } + } + + public int hashCode() { + return ObjectUtils.nullSafeHashCode(getName()) * 27 + ObjectUtils.nullSafeHashCode(getClassName()) * 27 + + ObjectUtils.nullSafeHashCode(getScope()) * 27; + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @param name the name to set + */ + public void setName(String name) { + if (StringUtils.hasText(name)) { + this.name = name; + } else { + this.name = null; + } + } + + /** + * @return the class name + */ + public String getClassName() { + return className; + } + + /** + * @param className the class name to set + */ + public void setClassName(String className) { + if (StringUtils.hasText(className)) { + this.className = className; + } else { + this.className = null; + } + } + + /** + * @return the scope + */ + public String getScope() { + return scope; + } + + /** + * @param scope the scope to set + */ + public void setScope(String scope) { + if (StringUtils.hasText(scope)) { + this.scope = scope; + } else { + this.scope = null; + } + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/ViewStateModel.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/ViewStateModel.java new file mode 100644 index 00000000..d8048a03 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/ViewStateModel.java @@ -0,0 +1,339 @@ +/* + * 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.model; + +import java.util.LinkedList; + +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Model support for view states. + *

+ * A state where the user participates. When a view state is entered, this flow pauses and control goes to the user. + * After some think time, the user resumes this flow at the view-state by signaling an event. + *

+ * Once paused, a view-state may be 'refreshed' by the user. A refresh causes the response to be reissued and then + * returns control back to the user. + *

+ * A view state may be configured with one or more render-actions using the 'on-render' element. Render actions are + * executed immediately before the view is rendered. + *

+ * A view state is a transitionable state. A view state transition is triggered by a user event. + * + * @author Scott Andrews + */ +public class ViewStateModel extends AbstractTransitionableStateModel { + private String view; + private String redirect; + private String popup; + private String model; + private LinkedList vars; + private LinkedList onRenderActions; + + /** + * Create a view state model + * @param id the identifier of the state + */ + public ViewStateModel(String id) { + setId(id); + } + + /** + * Create a view state model + * @param id the identifier of the state + * @param view the view to render + */ + public ViewStateModel(String id, String view) { + setId(id); + setView(view); + } + + /** + * Create a view state model + * @param id the identifier of the state + * @param view the view to render + * @param redirect request a flow execution redirect before render + * @param popup view should render in a popup dialog + * @param model the model object to bind for this view + * @param vars variables for this state + * @param onRenderActions actions to be executed before rendering + * @param attributes meta attributes for this state + * @param secured the security settings for this state + * @param onEntryActions actions to be executed on entry + * @param exceptionHandlers exception handlers for this state + * @param transitions transitions for this state + * @param onExitActions actions to be executed before exiting + */ + public ViewStateModel(String id, String view, String redirect, String popup, String model, LinkedList vars, + LinkedList onRenderActions, LinkedList attributes, SecuredModel secured, LinkedList onEntryActions, + LinkedList exceptionHandlers, LinkedList transitions, LinkedList onExitActions) { + setId(id); + setView(view); + setRedirect(redirect); + setPopup(popup); + setModel(model); + setVars(vars); + setOnRenderActions(onRenderActions); + setAttributes(attributes); + setSecured(secured); + setOnEntryActions(onEntryActions); + setExceptionHandlers(exceptionHandlers); + setTransitions(transitions); + setOnExitActions(onExitActions); + } + + /** + * Merge properties + * @param model the view state to merge into this state + */ + public void merge(Model model) { + if (isMergeableWith(model)) { + ViewStateModel state = (ViewStateModel) model; + setAttributes(merge(getAttributes(), state.getAttributes())); + setSecured((SecuredModel) merge(getSecured(), state.getSecured())); + setOnEntryActions(merge(getOnEntryActions(), state.getOnEntryActions(), false)); + setExceptionHandlers(merge(getExceptionHandlers(), state.getExceptionHandlers())); + setTransitions(merge(getTransitions(), state.getTransitions())); + setOnExitActions(merge(getOnExitActions(), state.getOnExitActions(), false)); + setView(merge(getView(), state.getView())); + setRedirect(merge(getRedirect(), state.getRedirect())); + setPopup(merge(getPopup(), state.getPopup())); + setModel(merge(getModel(), state.getModel())); + setVars(merge(getVars(), state.getVars(), false)); + setOnRenderActions(merge(getOnRenderActions(), state.getOnRenderActions(), false)); + } + } + + /** + * Tests if the model is able to be merged with this view state + * @param model the model to test + */ + public boolean isMergeableWith(Model model) { + if (model == null) { + return false; + } + if (!(model instanceof ViewStateModel)) { + return false; + } + ViewStateModel state = (ViewStateModel) model; + return ObjectUtils.nullSafeEquals(getId(), state.getId()); + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (!(obj instanceof ViewStateModel)) { + return false; + } + ViewStateModel state = (ViewStateModel) obj; + if (state == null) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getId(), state.getId())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getAttributes(), state.getAttributes())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getSecured(), state.getSecured())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getOnEntryActions(), state.getOnEntryActions())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getExceptionHandlers(), state.getExceptionHandlers())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getTransitions(), state.getTransitions())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getOnExitActions(), state.getOnExitActions())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getView(), state.getView())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getRedirect(), state.getRedirect())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getPopup(), state.getPopup())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getModel(), state.getModel())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getVars(), state.getVars())) { + return false; + } else if (!ObjectUtils.nullSafeEquals(getOnRenderActions(), state.getOnRenderActions())) { + return false; + } else { + return true; + } + } + + public int hashCode() { + return ObjectUtils.nullSafeHashCode(getId()) * 27 + ObjectUtils.nullSafeHashCode(getAttributes()) * 27 + + ObjectUtils.nullSafeHashCode(getSecured()) * 27 + ObjectUtils.nullSafeHashCode(getOnEntryActions()) + * 27 + ObjectUtils.nullSafeHashCode(getExceptionHandlers()) * 27 + + ObjectUtils.nullSafeHashCode(getTransitions()) * 27 + + ObjectUtils.nullSafeHashCode(getOnExitActions()) * 27 + ObjectUtils.nullSafeHashCode(getView()) * 27 + + ObjectUtils.nullSafeHashCode(getRedirect()) * 27 + ObjectUtils.nullSafeHashCode(getPopup()) * 27 + + ObjectUtils.nullSafeHashCode(getModel()) * 27 + ObjectUtils.nullSafeHashCode(getVars()) * 27 + + ObjectUtils.nullSafeHashCode(getOnRenderActions()) * 27; + } + + /** + * @return the view + */ + public String getView() { + return view; + } + + /** + * @param view the view to set + */ + public void setView(String view) { + if (StringUtils.hasText(view)) { + this.view = view; + } else { + this.view = null; + } + } + + /** + * @return the redirect + */ + public String getRedirect() { + return redirect; + } + + /** + * @param redirect the redirect to set + */ + public void setRedirect(String redirect) { + if (StringUtils.hasText(redirect)) { + this.redirect = redirect; + } else { + this.redirect = null; + } + } + + /** + * @return the popup + */ + public String getPopup() { + return popup; + } + + /** + * @param popup the popup to set + */ + public void setPopup(String popup) { + if (StringUtils.hasText(popup)) { + this.popup = popup; + } else { + this.popup = null; + } + } + + /** + * @return the model + */ + public String getModel() { + return model; + } + + /** + * @param model the model to set + */ + public void setModel(String model) { + if (StringUtils.hasText(model)) { + this.model = model; + } else { + this.model = null; + } + } + + /** + * @return the vars + */ + public LinkedList getVars() { + return vars; + } + + /** + * @param vars the vars to set + */ + public void setVars(LinkedList vars) { + this.vars = vars; + } + + /** + * @param var the var to add + */ + public void addVar(VarModel var) { + if (var == null) { + return; + } + if (vars == null) { + vars = new LinkedList(); + } + vars.add(var); + } + + /** + * @param vars the vars to add + */ + public void addVars(LinkedList vars) { + if (vars == null || vars.isEmpty()) { + return; + } + if (this.vars == null) { + this.vars = new LinkedList(); + } + this.vars.addAll(vars); + } + + /** + * @return the on render actions + */ + public LinkedList getOnRenderActions() { + return onRenderActions; + } + + /** + * @param onRenderActions the on render actions to set + */ + public void setOnRenderActions(LinkedList onRenderActions) { + this.onRenderActions = onRenderActions; + } + + /** + * @param onRenderAction the on render action to add + */ + public void addOnRenderAction(AbstractActionModel onRenderAction) { + if (onRenderAction == null) { + return; + } + if (this.onRenderActions == null) { + this.onRenderActions = new LinkedList(); + } + this.onRenderActions.add(onRenderAction); + } + + /** + * @param onRenderActions the on render actions to add + */ + public void addOnRenderActions(LinkedList onRenderActions) { + if (onRenderActions == null || onRenderActions.isEmpty()) { + return; + } + if (this.onRenderActions == null) { + this.onRenderActions = new LinkedList(); + } + this.onRenderActions.addAll(onRenderActions); + } + +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/FlowModelBuilder.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/FlowModelBuilder.java new file mode 100644 index 00000000..54245f09 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/FlowModelBuilder.java @@ -0,0 +1,70 @@ +/* + * Copyright 2004-2008 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.model.builder; + +import org.springframework.webflow.engine.model.FlowModel; + +/** + * Builder interface used to build a flow model. The process of building a flow model consists of the following steps: + *

    + *
  1. Initialize this builder by calling {@link #init()}. + *
  2. Call {@link #build()} to create the flow model. + *
  3. Call {@link #getFlowModel()} to return the fully-built {@link FlowModel} model. + *
  4. Dispose this builder, releasing any resources allocated during the building process by calling + * {@link #dispose()}. + *
+ *

+ * Implementations should encapsulate flow construction logic, either for a specific kind of flow, for example, an + * XmlFlowModelBuilder, for building flows from an XML-definition. + *

+ * This is a good example of the classic GoF builder pattern. + * + * @see FlowModel + * + * @author Keith Donald + * @author Erwin Vervaet + * @author Scott Andrews + */ +public interface FlowModelBuilder { + + /** + * Initialize this builder. This could cause the builder to open a stream to an externalized resource representing + * the flow definition, for example. + * @throws FlowModelBuilderException an exception occurred building the flow + */ + public void init() throws FlowModelBuilderException; + + /** + * Builds any variables initialized by the flow when it starts. + * @throws FlowModelBuilderException an exception occurred building the flow + */ + public void build() throws FlowModelBuilderException; + + /** + * Get the fully constructed flow model. Called by the builder's assembler (director) after assembly. When this + * method is called by the assembler, it is expected flow construction has completed and the returned flow model is + * ready for use. + * @throws FlowModelBuilderException an exception occurred building this flow + */ + public FlowModel getFlowModel() throws FlowModelBuilderException; + + /** + * Shutdown the builder, releasing any resources it holds. A new flow construction process should start with another + * call to the {@link #init()} method. + * @throws FlowModelBuilderException an exception occurred disposing this flow + */ + public void dispose() throws FlowModelBuilderException; +} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/FlowModelBuilderException.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/FlowModelBuilderException.java new file mode 100644 index 00000000..890b2896 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/FlowModelBuilderException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2004-2008 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.model.builder; + +import org.springframework.webflow.core.FlowException; + +/** + * Exception thrown to indicate a problem while building a flow model. + * + * @see FlowModelBuilder + * + * @author Erwin Vervaet + * @author Scott Andrews + */ +public class FlowModelBuilderException extends FlowException { + + /** + * Create a new flow model builder exception. + * @param message descriptive message + */ + public FlowModelBuilderException(String message) { + super(message); + } + + /** + * Create a new flow model builder exception. + * @param message descriptive message + * @param cause the underlying cause of this exception + */ + public FlowModelBuilderException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/DefaultDocumentLoader.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/DefaultDocumentLoader.java similarity index 95% rename from spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/DefaultDocumentLoader.java rename to spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/DefaultDocumentLoader.java index ba739b5b..3aa663b3 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/DefaultDocumentLoader.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/DefaultDocumentLoader.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.webflow.engine.builder.xml; +package org.springframework.webflow.engine.model.builder.xml; import java.io.IOException; import java.io.InputStream; diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/DocumentLoader.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/DocumentLoader.java similarity index 93% rename from spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/DocumentLoader.java rename to spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/DocumentLoader.java index de5f0510..9603976c 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/DocumentLoader.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/DocumentLoader.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.webflow.engine.builder.xml; +package org.springframework.webflow.engine.model.builder.xml; import java.io.IOException; diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/WebFlowEntityResolver.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/WebFlowEntityResolver.java similarity index 94% rename from spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/WebFlowEntityResolver.java rename to spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/WebFlowEntityResolver.java index 493eff8e..b8d7fe61 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/WebFlowEntityResolver.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/WebFlowEntityResolver.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.webflow.engine.builder.xml; +package org.springframework.webflow.engine.model.builder.xml; import java.io.IOException; diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/XmlFlowModelBuilder.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/XmlFlowModelBuilder.java new file mode 100644 index 00000000..2198e65e --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/XmlFlowModelBuilder.java @@ -0,0 +1,589 @@ +/* + * 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.model.builder.xml; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import javax.xml.parsers.ParserConfigurationException; + +import org.springframework.core.io.Resource; +import org.springframework.core.style.ToStringCreator; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; +import org.springframework.webflow.engine.model.AbstractActionModel; +import org.springframework.webflow.engine.model.AbstractStateModel; +import org.springframework.webflow.engine.model.ActionStateModel; +import org.springframework.webflow.engine.model.AttributeModel; +import org.springframework.webflow.engine.model.BeanImportModel; +import org.springframework.webflow.engine.model.DecisionStateModel; +import org.springframework.webflow.engine.model.EndStateModel; +import org.springframework.webflow.engine.model.EvaluateModel; +import org.springframework.webflow.engine.model.ExceptionHandlerModel; +import org.springframework.webflow.engine.model.FlowModel; +import org.springframework.webflow.engine.model.IfModel; +import org.springframework.webflow.engine.model.InputModel; +import org.springframework.webflow.engine.model.OutputModel; +import org.springframework.webflow.engine.model.PersistenceContextModel; +import org.springframework.webflow.engine.model.RenderModel; +import org.springframework.webflow.engine.model.SecuredModel; +import org.springframework.webflow.engine.model.SetModel; +import org.springframework.webflow.engine.model.SubflowStateModel; +import org.springframework.webflow.engine.model.TransitionModel; +import org.springframework.webflow.engine.model.VarModel; +import org.springframework.webflow.engine.model.ViewStateModel; +import org.springframework.webflow.engine.model.builder.FlowModelBuilder; +import org.springframework.webflow.engine.model.builder.FlowModelBuilderException; +import org.springframework.webflow.engine.model.registry.FlowModelRegistry; +import org.springframework.webflow.engine.model.registry.NoSuchFlowModelException; +import org.springframework.webflow.util.ResourceHolder; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +public class XmlFlowModelBuilder implements FlowModelBuilder, ResourceHolder { + + /** + * The resource from which the document element being parsed was read. Used as a location for relative resource + * lookup. + */ + protected Resource resource; + + /** + * The flow model registry used to lookup other flows + */ + protected FlowModelRegistry registry; + + /** + * The loader for loading the flow definition resource XML document. + */ + private DocumentLoader documentLoader = new DefaultDocumentLoader(); + + /** + * The in-memory document object model (DOM) of the XML Document read from the flow definition resource. + */ + private Document document; + + /** + * The flow model. + */ + private FlowModel flowModel; + + /** + * Create a new XML flow builder parsing the document at the specified location, using the provided service locator + * to access externally managed flow artifacts. + */ + public XmlFlowModelBuilder(Resource resource, FlowModelRegistry registry) { + this.resource = resource; + this.registry = registry; + } + + /** + * Sets the loader that will load the XML-based flow definition document. Optional, defaults to + * {@link DefaultDocumentLoader}. + * @param documentLoader the document loader + */ + public void setDocumentLoader(DocumentLoader documentLoader) { + Assert.notNull(documentLoader, "The XML document loader is required"); + this.documentLoader = documentLoader; + } + + public void init() throws FlowModelBuilderException { + try { + document = documentLoader.loadDocument(resource); + } catch (IOException e) { + throw new FlowModelBuilderException("Could not access the XML flow definition resource at " + resource, e); + } catch (ParserConfigurationException e) { + throw new FlowModelBuilderException("Could not configure the parser to parse the XML flow definition at " + + resource, e); + } catch (SAXException e) { + throw new FlowModelBuilderException("Could not parse the XML flow definition document at " + resource, e); + } + } + + public void build() throws FlowModelBuilderException { + if (getDocumentElement() == null) { + throw new FlowModelBuilderException("The FlowModelBuilder must be initialized first"); + } + flowModel = parseFlow(getDocumentElement()); + if (flowModel.getParent() != null) { + for (Iterator parentIt = Arrays.asList(StringUtils.trimArrayElements(flowModel.getParent().split(","))) + .iterator(); parentIt.hasNext();) { + String parentFlowId = (String) parentIt.next(); + if (StringUtils.hasText(parentFlowId)) { + try { + flowModel.merge(registry.getFlowModel(parentFlowId)); + } catch (NoSuchFlowModelException e) { + throw new FlowModelBuilderException("Unable to find flow '" + parentFlowId + + "' to inherit from", e); + } + } + } + } + } + + public FlowModel getFlowModel() throws FlowModelBuilderException { + return flowModel; + } + + public void dispose() throws FlowModelBuilderException { + document = null; + flowModel = null; + } + + protected FlowModel parseFlow(Element ele) { + FlowModel flow = new FlowModel(); + flow.setParent(ele.getAttribute("parent")); + flow.setStartStateId(ele.getAttribute("start-state")); + flow.addAttributes(parseAttributes(ele)); + flow.setSecured(parseSecured(ele)); + flow.setPersistenceContext(parsePersistenceContext(ele)); + flow.addVars(parseVars(ele)); + flow.addInputs(parseInputs(ele)); + flow.addOutputs(parseOutputs(ele)); + flow.addOnStartActions(parseOnStartActions(ele)); + flow.addStates(parseStates(ele)); + flow.addGlobalTransitions(parseGlobalTransitions(ele)); + flow.addOnEndActions(parseOnEndActions(ele)); + flow.addExceptionHandlers(parseExceptionHandlers(ele)); + flow.addBeanImports(parseBeanImports(ele)); + return flow; + } + + protected LinkedList parseAttributes(Element ele) { + LinkedList attributes = new LinkedList(); + for (Iterator attributeIt = DomUtils.getChildElementsByTagName(ele, "attribute").iterator(); attributeIt + .hasNext();) { + attributes.add(parseAttribute((Element) attributeIt.next())); + } + return attributes; + } + + protected LinkedList parseVars(Element ele) { + LinkedList vars = new LinkedList(); + for (Iterator varIt = DomUtils.getChildElementsByTagName(ele, "var").iterator(); varIt.hasNext();) { + vars.add(parseVar((Element) varIt.next())); + } + return vars; + } + + protected LinkedList parseInputs(Element ele) { + LinkedList inputs = new LinkedList(); + for (Iterator inputIt = DomUtils.getChildElementsByTagName(ele, "input").iterator(); inputIt.hasNext();) { + inputs.add(parseInput((Element) inputIt.next())); + } + return inputs; + } + + protected LinkedList parseOutputs(Element ele) { + LinkedList outputs = new LinkedList(); + for (Iterator outputIt = DomUtils.getChildElementsByTagName(ele, "output").iterator(); outputIt.hasNext();) { + outputs.add(parseOutput((Element) outputIt.next())); + } + return outputs; + } + + protected LinkedList parseActions(Element ele) { + LinkedList actions = new LinkedList(); + for (Iterator actionIt = getChildElementsByTagNames(ele, new String[] { "evaluate", "render", "set" }) + .iterator(); actionIt.hasNext();) { + actions.add(parseAction((Element) actionIt.next())); + } + return actions; + } + + protected LinkedList parseStates(Element ele) { + LinkedList states = new LinkedList(); + for (Iterator stateIt = getChildElementsByTagNames(ele, + new String[] { "action-state", "view-state", "decision-state", "subflow-state", "end-state" }) + .iterator(); stateIt.hasNext();) { + states.add(parseState((Element) stateIt.next())); + } + return states; + } + + protected LinkedList parseTransitions(Element ele) { + LinkedList transitions = new LinkedList(); + for (Iterator transitionIt = DomUtils.getChildElementsByTagName(ele, "transition").iterator(); transitionIt + .hasNext();) { + transitions.add(parseTransition((Element) transitionIt.next())); + } + return transitions; + + } + + protected LinkedList parseExceptionHandlers(Element ele) { + LinkedList exceptionHandlers = new LinkedList(); + for (Iterator exceptionHandlerIt = DomUtils.getChildElementsByTagName(ele, "exception-handler").iterator(); exceptionHandlerIt + .hasNext();) { + exceptionHandlers.add(parseExceptionHandler((Element) exceptionHandlerIt.next())); + } + return exceptionHandlers; + } + + protected LinkedList parseBeanImports(Element ele) { + LinkedList beanImports = new LinkedList(); + for (Iterator beanImportIt = DomUtils.getChildElementsByTagName(ele, "bean-import").iterator(); beanImportIt + .hasNext();) { + beanImports.add(parseBeanImport((Element) beanImportIt.next())); + } + return beanImports; + } + + protected LinkedList parseIfs(Element ele) { + LinkedList ifs = new LinkedList(); + for (Iterator ifIt = DomUtils.getChildElementsByTagName(ele, "if").iterator(); ifIt.hasNext();) { + ifs.add(parseIf((Element) ifIt.next())); + } + return ifs; + } + + protected AbstractActionModel parseAction(Element ele) { + if (DomUtils.nodeNameEquals(ele, "evaluate")) { + return parseEvaluate(ele); + } else if (DomUtils.nodeNameEquals(ele, "render")) { + return parseRender(ele); + } else if (DomUtils.nodeNameEquals(ele, "set")) { + return parseSet(ele); + } else { + throw new UnsupportedOperationException("Unknown action element encountered '" + ele.getLocalName() + "'"); + } + } + + protected AbstractStateModel parseState(Element ele) { + if (DomUtils.nodeNameEquals(ele, "action-state")) { + return parseActionState(ele); + } else if (DomUtils.nodeNameEquals(ele, "view-state")) { + return parseViewState(ele); + } else if (DomUtils.nodeNameEquals(ele, "decision-state")) { + return parseDecisionState(ele); + } else if (DomUtils.nodeNameEquals(ele, "subflow-state")) { + return parseSubflowState(ele); + } else if (DomUtils.nodeNameEquals(ele, "end-state")) { + return parseEndState(ele); + } else { + throw new UnsupportedOperationException("Unknown state element encountered '" + ele.getLocalName() + "'"); + } + } + + protected LinkedList parseGlobalTransitions(Element ele) { + if (ele == null) { + return null; + } else if (!DomUtils.nodeNameEquals(ele, "global-transitions")) { + return parseGlobalTransitions(DomUtils.getChildElementByTagName(ele, "global-transitions")); + } else { + return parseTransitions(ele); + } + } + + protected AttributeModel parseAttribute(Element ele) { + if (ele == null) { + return null; + } else if (!DomUtils.nodeNameEquals(ele, "attribute")) { + return parseAttribute(DomUtils.getChildElementByTagName(ele, "attribute")); + } else { + return new AttributeModel(ele.getAttribute("name"), parseValue(ele), ele.getAttribute("type")); + } + } + + protected String parseValue(Element ele) { + if (ele.hasAttribute("value")) { + return ele.getAttribute("value"); + } else { + Element valueEle = DomUtils.getChildElementByTagName(ele, "value"); + return valueEle != null ? DomUtils.getTextValue(valueEle) : null; + } + } + + protected SecuredModel parseSecured(Element ele) { + if (ele == null) { + return null; + } else if (!DomUtils.nodeNameEquals(ele, "secured")) { + return parseSecured(DomUtils.getChildElementByTagName(ele, "secured")); + } else { + return new SecuredModel(ele.getAttribute("attributes"), ele.getAttribute("match")); + } + } + + protected PersistenceContextModel parsePersistenceContext(Element ele) { + if (ele == null) { + return null; + } else if (!DomUtils.nodeNameEquals(ele, "persistence-context")) { + return parsePersistenceContext(DomUtils.getChildElementByTagName(ele, "persistence-context")); + } else { + return new PersistenceContextModel(); + } + } + + protected VarModel parseVar(Element ele) { + if (ele == null) { + return null; + } else if (!DomUtils.nodeNameEquals(ele, "var")) { + return parseVar(DomUtils.getChildElementByTagName(ele, "var")); + } else { + return new VarModel(ele.getAttribute("name"), ele.getAttribute("class"), ele.getAttribute("scope")); + } + } + + protected InputModel parseInput(Element ele) { + if (ele == null) { + return null; + } else if (!DomUtils.nodeNameEquals(ele, "input")) { + return parseInput(DomUtils.getChildElementByTagName(ele, "input")); + } else { + return new InputModel(ele.getAttribute("name"), ele.getAttribute("value"), ele.getAttribute("type"), ele + .getAttribute("required")); + } + } + + protected OutputModel parseOutput(Element ele) { + if (ele == null) { + return null; + } else if (!DomUtils.nodeNameEquals(ele, "output")) { + return parseOutput(DomUtils.getChildElementByTagName(ele, "output")); + } else { + return new OutputModel(ele.getAttribute("name"), ele.getAttribute("value"), ele.getAttribute("type"), ele + .getAttribute("required")); + } + } + + protected TransitionModel parseTransition(Element ele) { + if (ele == null) { + return null; + } else if (!DomUtils.nodeNameEquals(ele, "transition")) { + return parseTransition(DomUtils.getChildElementByTagName(ele, "transition")); + } else { + return new TransitionModel(ele.getAttribute("on"), ele.getAttribute("to"), + ele.getAttribute("on-exception"), ele.getAttribute("bind"), parseAttributes(ele), + parseSecured(ele), parseActions(ele)); + } + } + + protected ExceptionHandlerModel parseExceptionHandler(Element ele) { + if (ele == null) { + return null; + } else if (!DomUtils.nodeNameEquals(ele, "exception-handler")) { + return parseExceptionHandler(DomUtils.getChildElementByTagName(ele, "exception-handler")); + } else { + return new ExceptionHandlerModel(ele.getAttribute("bean-name")); + } + } + + protected BeanImportModel parseBeanImport(Element ele) { + if (ele == null) { + return null; + } else if (!DomUtils.nodeNameEquals(ele, "bean-import")) { + return parseBeanImport(DomUtils.getChildElementByTagName(ele, "bean-import")); + } else { + return new BeanImportModel(ele.getAttribute("resource")); + } + } + + protected IfModel parseIf(Element ele) { + if (ele == null) { + return null; + } else if (!DomUtils.nodeNameEquals(ele, "if")) { + return parseIf(DomUtils.getChildElementByTagName(ele, "if")); + } else { + return new IfModel(ele.getAttribute("test"), ele.getAttribute("then"), ele.getAttribute("else")); + } + } + + protected LinkedList parseOnStartActions(Element ele) { + if (ele == null) { + return null; + } else if (!DomUtils.nodeNameEquals(ele, "on-start")) { + return parseOnStartActions(DomUtils.getChildElementByTagName(ele, "on-start")); + } else { + return parseActions(ele); + } + } + + protected LinkedList parseOnEntryActions(Element ele) { + if (ele == null) { + return null; + } else if (!DomUtils.nodeNameEquals(ele, "on-entry")) { + return parseOnEntryActions(DomUtils.getChildElementByTagName(ele, "on-entry")); + } else { + return parseActions(ele); + } + } + + protected LinkedList parseOnExitActions(Element ele) { + if (ele == null) { + return null; + } else if (!DomUtils.nodeNameEquals(ele, "on-exit")) { + return parseOnExitActions(DomUtils.getChildElementByTagName(ele, "on-exit")); + } else { + return parseActions(ele); + } + } + + protected LinkedList parseOnRenderActions(Element ele) { + if (ele == null) { + return null; + } else if (!DomUtils.nodeNameEquals(ele, "on-render")) { + return parseOnRenderActions(DomUtils.getChildElementByTagName(ele, "on-render")); + } else { + return parseActions(ele); + } + } + + protected LinkedList parseOnEndActions(Element ele) { + if (ele == null) { + return null; + } else if (!DomUtils.nodeNameEquals(ele, "on-end")) { + return parseOnEndActions(DomUtils.getChildElementByTagName(ele, "on-end")); + } else { + return parseActions(ele); + } + } + + protected EvaluateModel parseEvaluate(Element ele) { + if (ele == null) { + return null; + } else if (!DomUtils.nodeNameEquals(ele, "evaluate")) { + return parseEvaluate(DomUtils.getChildElementByTagName(ele, "evaluate")); + } else { + return new EvaluateModel(ele.getAttribute("expression"), ele.getAttribute("result"), ele + .getAttribute("result-type")); + } + } + + protected RenderModel parseRender(Element ele) { + if (ele == null) { + return null; + } else if (!DomUtils.nodeNameEquals(ele, "render")) { + return parseRender(DomUtils.getChildElementByTagName(ele, "render")); + } else { + return new RenderModel(ele.getAttribute("fragments")); + } + } + + protected SetModel parseSet(Element ele) { + if (ele == null) { + return null; + } else if (!DomUtils.nodeNameEquals(ele, "set")) { + return parseSet(DomUtils.getChildElementByTagName(ele, "set")); + } else { + return new SetModel(ele.getAttribute("name"), ele.getAttribute("value"), ele.getAttribute("type")); + } + } + + protected ActionStateModel parseActionState(Element ele) { + if (ele == null) { + return null; + } else if (!DomUtils.nodeNameEquals(ele, "action-state")) { + return parseActionState(DomUtils.getChildElementByTagName(ele, "action-state")); + } else { + return new ActionStateModel(ele.getAttribute("id"), parseAttributes(ele), parseSecured(ele), + parseOnEntryActions(ele), parseTransitions(ele), parseOnExitActions(ele), parseActions(ele), + parseExceptionHandlers(ele)); + } + } + + protected ViewStateModel parseViewState(Element ele) { + if (ele == null) { + return null; + } else if (!DomUtils.nodeNameEquals(ele, "view-state")) { + return parseViewState(DomUtils.getChildElementByTagName(ele, "view-state")); + } else { + return new ViewStateModel(ele.getAttribute("id"), ele.getAttribute("view"), ele.getAttribute("redirect"), + ele.getAttribute("popup"), ele.getAttribute("model"), parseVars(ele), parseOnRenderActions(ele), + parseAttributes(ele), parseSecured(ele), parseOnEntryActions(ele), parseExceptionHandlers(ele), + parseTransitions(ele), parseOnExitActions(ele)); + } + } + + protected DecisionStateModel parseDecisionState(Element ele) { + if (ele == null) { + return null; + } else if (!DomUtils.nodeNameEquals(ele, "decision-state")) { + return parseDecisionState(DomUtils.getChildElementByTagName(ele, "decision-state")); + } else { + return new DecisionStateModel(ele.getAttribute("id"), parseIfs(ele), parseOnExitActions(ele), + parseAttributes(ele), parseSecured(ele), parseOnEntryActions(ele), parseExceptionHandlers(ele)); + } + } + + protected SubflowStateModel parseSubflowState(Element ele) { + if (ele == null) { + return null; + } else if (!DomUtils.nodeNameEquals(ele, "subflow-state")) { + return parseSubflowState(DomUtils.getChildElementByTagName(ele, "subflow-state")); + } else { + return new SubflowStateModel(ele.getAttribute("id"), ele.getAttribute("subflow"), ele + .getAttribute("subflow-attribute-mapper"), parseInputs(ele), parseOutputs(ele), + parseAttributes(ele), parseSecured(ele), parseOnEntryActions(ele), parseExceptionHandlers(ele), + parseTransitions(ele), parseOnExitActions(ele)); + } + } + + protected EndStateModel parseEndState(Element ele) { + if (ele == null) { + return null; + } else if (!DomUtils.nodeNameEquals(ele, "end-state")) { + return parseEndState(DomUtils.getChildElementByTagName(ele, "end-state")); + } else { + return new EndStateModel(ele.getAttribute("id"), ele.getAttribute("view-factory"), ele + .getAttribute("commit"), parseOutputs(ele), parseAttributes(ele), parseSecured(ele), + parseOnEntryActions(ele), parseExceptionHandlers(ele)); + } + } + + // TODO: submit this to DomUtils + private static List getChildElementsByTagNames(Element ele, String[] childEleNames) { + List names = Arrays.asList(childEleNames); + NodeList nl = ele.getChildNodes(); + List childEles = new LinkedList(); + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + if (node instanceof Element && (names.contains(node.getLocalName()) || names.contains(node.getNodeName()))) { + childEles.add(node); + } + } + return childEles; + } + + public Resource getResource() { + return resource; + } + + /** + * Returns the DOM document parsed from the XML file. + */ + protected Document getDocument() { + return document; + } + + /** + * Returns the root document element. + */ + protected Element getDocumentElement() { + return document != null ? document.getDocumentElement() : null; + } + + public String toString() { + return new ToStringCreator(this).append("location", resource).toString(); + } + +} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/spring-webflow-2.0.xsd b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/spring-webflow-2.0.xsd similarity index 52% rename from spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/spring-webflow-2.0.xsd rename to spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/spring-webflow-2.0.xsd index 73cada43..9360a7b6 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/engine/builder/xml/spring-webflow-2.0.xsd +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/builder/xml/spring-webflow-2.0.xsd @@ -1,45 +1,33 @@ - - - - - - -This schema defines Spring Web Flow's XML-based flow definition language. -
-The root "flow" element in this document defines exactly one flow definition. -A flow definition is a blueprint for a carrying out a conversation with a single user. -
-A flow is composed of one or more states that form the steps of the flow. -Each state executes a behavior when entered. What behavior is executed is a -function of the state's type. Core state types include view states, -action states, subflow states, decision states, and end states. -
-A flow definition has exactly one start state. -Events that occur within states drive state transitions. -]]> -
-
- - - - - - - - - - - - - + + + + + + +This schema defines Spring Web Flow's XML-based flow definition language. +
+The root "flow" element in this document defines exactly one flow definition. +A flow definition is a blueprint for a carrying out a conversation with a single user. +
+A flow is composed of one or more states that form the steps of the flow. +Each state executes a behavior when entered. What behavior is executed is a +function of the state's type. Core state types include view states, +action states, subflow states, decision states, and end states. +
+A flow definition has exactly one start state. +Events that occur within states drive state transitions. +]]> +
+
+ + @@ -77,60 +65,113 @@ The unique identifier of this state; must be unique to this flow. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + -A flow may also exhibit the following characteristics: -

+]]> + + + + + + + + + + - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Once paused, a view-state may be 'refreshed' by the user. -A refresh causes the response to be reissued and then returns control back to the user. -
-A view state may be configured with one or more render-actions using the 'on-render' element. -Render actions are executed immediately before the view is rendered. -
-A view state is a transitionable state. -A view state transition is triggered by a user event. -]]> -
-
-
- - - - -A decision state is a transitionable state. -A decision state transition can be triggered by evaluating a boolean expression against the flow execution request context. -To define transition expressions, use the 'if' element. -
-Examples: -
-A simple boolean expression test, using the convenient 'if' element: -
-    <decision-state id="requiresShipping">
-	    <if test="#{sale.requiresShipping}" then="enterShippingDetails" else="processSale"/>
-    </decision-state>
-
-]]> -
-
-
- - - - -A subflow state is a transitionable state. -A transition is triggered by the subflow outcome that was reached. -]]> - - - - - - - -An end state is not transitionable; there are never transitions out of an end state. -When an end-state is entered, an instance of this flow is terminated. -
-When this flow terminates, if it was the "root" flow the entire execution is terminated. -If this flow was a subflow, its parent flow resumes. -]]> -
-
-
-
- - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - -
- - - - + + + + - - - -
- - - - - - - - - - - - - - - - - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + -
  • request - The variable goes out of scope when a call to start this flow completes. -
  • flash - The variable goes out of scope when the next user event is signaled.
  • flow - The variable goes out of scope when this flow session ends.
  • conversation - The variable goes out of scope when the overall conversation governing this flow ends. -]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + - - - - - - - - <transition on="submit" to="state"/> - -... which reads "on the occurrence of the 'submit' event, transition to 'state'" -
    -Sophisticated transitional expressions are also supported when enclosed in a delimited expression: -
    -	<transition on="#{event == 'submit' &;amp;& flowScope.attribute == 'foo'}" to="state"/>
    -
    -]]> -
    -
    -
    - - - - -The value of this attribute must be a fully-qualified java.lang.Exception class name (e.g. example.booking.ItineraryExpiredException). -Superclasses of the configured exception class match by default. Use this attribute or the 'on' attribute, not both. -]]> - - - - - - - -The value of this attribute may be a static state identifier (e.g. to="displayForm") or a dynamic expression (e.g. to="${flowScope.previousViewState}"). -If no value is specified, this transition acts as a simple event handler and will not change the state of the flow. -]]> - - - - -
    - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + - - - - - - - + + + + + + - - - - - - - + + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + +Once paused, a view-state may be 'refreshed' by the user. +A refresh causes the response to be reissued and then returns control back to the user. +
    +A view state may be configured with one or more render-actions using the 'on-render' element. +Render actions are executed immediately before the view is rendered. +
    +A view state is a transitionable state. +A view state transition is triggered by a user event. +]]> +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + priceForm.jsp -Can also be an evaluatable expression: -
    -	${flowScope.myViewExpression}
    -
    -Use the externalRedirect: prefix to redirect to an external URL, typically to interface with an external system. -External redirect query parameters may be specified using ${expressions} that evaluate against the request context: -
    -	externalRedirect:/http://someOtherSystem?orderId=${flowScope.order.id}&callbackUrl=#{flowExecutionUrl}
    -
    -Use the flowRedirect: prefix to redirect to another flow: -
    -	flowRedirect:myOtherFlow?someData=#{flowScope.data}
    -
    +Can also be an evaluatable expression: +
    +	${flowScope.myViewExpression}
    +
    +Use the externalRedirect: prefix to redirect to an external URL, typically to interface with an external system. +External redirect query parameters may be specified using ${expressions} that evaluate against the request context: +
    +	externalRedirect:/http://someOtherSystem?orderId=${flowScope.order.id}&callbackUrl=#{flowExecutionUrl}
    +
    +Use the flowRedirect: prefix to redirect to another flow: +
    +	flowRedirect:myOtherFlow?someData=#{flowScope.data}
    +
    When this attribute is not specified, the view to render will be determined by convention. -The default convention is to treat the id of this view state as the view identifier. -]]> -
    -
    -
    - - - - + + + + + + + - - - - + + + + + + + + + + + + + + + + + +
    +
    + +A decision state is a transitionable state. +A decision state transition can be triggered by evaluating a boolean expression against the flow execution request context. +To define transition expressions, use the 'if' element. +
    +Examples: +
    +A simple boolean expression test, using the convenient 'if' element: +
    +    <decision-state id="requiresShipping">
    +	    <if test="#{sale.requiresShipping}" then="enterShippingDetails" else="processSale"/>
    +    </decision-state>
    +
    ]]>
    - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - -The form is: -
    -	<if test="#{criteria}" then="trueStateId" else="falseStateId"/>
    -
    -]]> -
    -
    -
    - - - - - - - - - - - - - - -
    -
    -
    -
    -
    - - - - - - - - <if test="#{sale.requiresShipping} then="enterShippingDetails"/> - -]]> - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + +The form is: +
    +	<if test="#{criteria}" then="trueStateId" else="falseStateId"/>
    +
    +]]> +
    +
    + + + + + + <if test="#{sale.requiresShipping} then="enterShippingDetails"/> + +]]> + + + + + + + + + + + + + + - - - - -
    - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +A subflow state is a transitionable state. +A transition is triggered by the subflow outcome that was reached. +]]> + + + + + + + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - + + + + + + - - - - - - - + + + + + + - - - - - - - + + + + + + - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +An end state is not transitionable; there are never transitions out of an end state. +When an end-state is entered, an instance of this flow is terminated. +
    +When this flow terminates, if it was the "root" flow the entire execution is terminated. +If this flow was a subflow, its parent flow resumes. ]]>
    - - - - -
    - - - - - - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - + + + + + + - - - - - - - + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + When this attribute is not specified, no final response will be issued. In this case, the caller is expected to handle this flow outcome. -]]> - - - - - - - + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -For example: -
    -    <bean-import resource="orderitem-flow-beans.xml"/>
    -
    -... would look for 'orderitem-flow-beans.xml' in the same directory as this document. -]]> -
    -
    -
    -
    -
    +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +For example: +
    +    <bean-import resource="orderitem-flow-beans.xml"/>
    +
    +... would look for 'orderitem-flow-beans.xml' in the same directory as this document. +]]> +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Note: Cannot be used in conjunction with an exception based transition. +]]> + + + + + + + + + + <transition on="submit" to="state"/> + +... which reads "on the occurrence of the 'submit' event, transition to 'state'" +
    +Sophisticated transitional expressions are also supported when enclosed in a delimited expression: +
    +	<transition on="#{event == 'submit' &;amp;& flowScope.attribute == 'foo'}" to="state"/>
    +
    +]]> +
    +
    +
    + + + + +The value of this attribute must be a fully-qualified java.lang.Exception class name (e.g. example.booking.ItineraryExpiredException). +Superclasses of the configured exception class match by default. Use this attribute or the 'on' attribute, not both. +
    +Note: Cannot be used in conjunction with a secured element. +]]> +
    +
    +
    + + + + +The value of this attribute may be a static state identifier (e.g. to="displayForm") or a dynamic expression (e.g. to="${flowScope.previousViewState}"). +If no value is specified, this transition acts as a simple event handler and will not change the state of the flow. +]]> + + + +
    + + + + + + + + model attribute. Indicates whether binding should occur during this transition. Defaults to true. +]]> + + + + + + + + + + + + + + + + + + - - - - - + + + - - - - - - - + + + + + + - - - - - - - - + + + + + + + - - - - - - - + + + + + + - - - - - - - -
    - + + + + + + + + \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/DefaultFlowModelHolder.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/DefaultFlowModelHolder.java new file mode 100644 index 00000000..da1d96bb --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/DefaultFlowModelHolder.java @@ -0,0 +1,168 @@ +/* + * Copyright 2004-2008 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.model.registry; + +import java.io.IOException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.io.Resource; +import org.springframework.webflow.engine.model.FlowModel; +import org.springframework.webflow.engine.model.builder.FlowModelBuilder; +import org.springframework.webflow.engine.model.builder.FlowModelBuilderException; +import org.springframework.webflow.util.ResourceHolder; + +/** + * A flow model holder that can detect changes on an underlying flow model resource and refresh that resource + * automatically. + *

    + * This class is thread-safe. + *

    + * Note that this {@link FlowModel} holder uses a {@link FlowModelBuilder}. + * + * @see FlowModel + * + * @author Keith Donald + * @author Scott Andrews + */ +public class DefaultFlowModelHolder implements FlowModelHolder { + + private static final Log logger = LogFactory.getLog(DefaultFlowModelHolder.class); + + /** + * The flow model assembled by this assembler. + */ + private FlowModel flowModel; + + /** + * The flow mode identifier + */ + private String flowModelId; + + /** + * The flow model builder. + */ + private FlowModelBuilder builder; + + /** + * A last modified date for the backing flow definition resource, used to support automatic reassembly on resource + * change. + */ + private long lastModified; + + /** + * Creates a new refreshable flow model holder that uses the configured assembler (GOF director) to drive flow + * assembly, on initial use and on any resource change or refresh. + * @param builder the flow model builder to use + * @param flowModelId the identifier of the flow model + */ + public DefaultFlowModelHolder(FlowModelBuilder builder, String flowModelId) { + this.builder = builder; + this.flowModelId = flowModelId; + } + + /** + * Creates a new static flow model holder + * @param flowModel the flow model to hold + * @param flowModelId the identifier of the flow model + */ + public DefaultFlowModelHolder(FlowModel flowModel, String flowModelId) { + this.flowModel = flowModel; + this.flowModelId = flowModelId; + } + + public String getFlowModelId() { + return flowModelId; + } + + public synchronized FlowModel getFlowModel() throws FlowModelConstructionException { + if (flowModel == null) { + lastModified = calculateLastModified(); + logger.debug("Assembling the flow model for the first time"); + assembleFlow(); + } else { + refreshIfChanged(); + } + return flowModel; + } + + public synchronized void refresh() throws FlowModelConstructionException { + assembleFlow(); + } + + // internal helpers + + /** + * Helper that retrieves the last modified date by querying the backing flow resource. + * @return the last modified date, or 0L if it could not be retrieved + */ + private long calculateLastModified() { + if (getFlowModelBuilder() instanceof ResourceHolder) { + Resource resource = ((ResourceHolder) getFlowModelBuilder()).getResource(); + try { + 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 0L; + } + + /** + * Assemble the held flow definition, delegating to the configured FlowAssembler (director). + */ + private void assembleFlow() throws FlowModelConstructionException { + try { + builder.init(); + builder.build(); + flowModel = builder.getFlowModel(); + } catch (FlowModelBuilderException e) { + throw new FlowModelConstructionException(flowModelId, e); + } finally { + builder.dispose(); + } + } + + /** + * Reassemble the flow if its underlying resource has changed. + */ + private void refreshIfChanged() { + long calculatedLastModified = calculateLastModified(); + if (calculatedLastModified > lastModified) { + if (logger.isDebugEnabled()) { + logger.debug("Refreshing flow definition [" + flowModelId + "]"); + } + assembleFlow(); + lastModified = calculatedLastModified; + } + } + + /** + * Returns the flow builder that actually builds the Flow definition. + */ + private FlowModelBuilder getFlowModelBuilder() { + return builder; + } + + public String toString() { + return "'" + getFlowModelId() + "'"; + } + +} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/FlowModelConstructionException.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/FlowModelConstructionException.java new file mode 100644 index 00000000..1e76c2f6 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/FlowModelConstructionException.java @@ -0,0 +1,51 @@ +/* + * 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.model.registry; + +import org.springframework.webflow.core.FlowException; + +/** + * Thrown when a flow model was found during a lookup operation but could not be constructed. + * + * @author Keith Donald + * @author Erwin Vervaet + * @author Scott Andrews + */ +public class FlowModelConstructionException extends FlowException { + + /** + * The id of the flow that could not be constructed. + */ + private String flowModelId; + + /** + * Creates an exception indicating a flow model could not be constructed. + * @param flowModelId the flow model identifier + * @param cause the underlying cause of the exception + */ + public FlowModelConstructionException(String flowModelId, Throwable cause) { + super("An exception occurred constructing the flow '" + flowModelId + "'", cause); + this.flowModelId = flowModelId; + } + + /** + * Returns the id of the flow model that could not be constructed. + * @return the flow id + */ + public String getFlowModelId() { + return flowModelId; + } +} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/FlowModelHolder.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/FlowModelHolder.java new file mode 100644 index 00000000..fab3d4d7 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/FlowModelHolder.java @@ -0,0 +1,51 @@ +/* + * 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.model.registry; + +import org.springframework.webflow.engine.model.FlowModel; + +/** + * A holder holding a reference to a Flow model. Provides a layer of indirection, enabling things like "hot-reloadable" + * flow models. + * + * @see FlowModelRegistry#registerFlowModel(FlowModelHolder) + * + * @author Keith Donald + * @author Scott Andrews + */ +public interface FlowModelHolder { + + /** + * Returns the id of the flow model held by this holder. This is a lightweight method callers + * may call to obtain the id of the flow without triggering full flow definition assembly (which may be an expensive + * operation). + */ + public String getFlowModelId(); + + /** + * Returns the flow model held by this holder. Calling this method the first time may trigger flow assembly (which + * may be expensive). + * @throws FlowModelConstructionException if there is a problem constructing the target flow model + */ + public FlowModel getFlowModel() throws FlowModelConstructionException; + + /** + * Refresh the flow model held by this holder. Calling this method typically triggers flow re-assembly, which may + * include a refresh from an externalized resource such as a file. + * @throws FlowModelConstructionException if there is a problem constructing the target flow model + */ + public void refresh() throws FlowModelConstructionException; +} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/FlowModelLocator.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/FlowModelLocator.java new file mode 100644 index 00000000..5746dc32 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/FlowModelLocator.java @@ -0,0 +1,38 @@ +/* + * 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.model.registry; + +import org.springframework.webflow.engine.model.FlowModel; + +/** + * A runtime service locator interface for retrieving flow definitions by id. Flow locators are needed + * by flow executors at runtime to retrieve flow models to support loading flow definitions. + * + * @author Keith Donald + * @author Erwin Vervaet + * @author Scott Andrews + */ +public interface FlowModelLocator { + + /** + * Lookup the flow model with the specified id. + * @param id the flow model identifier + * @return the flow mode + * @throws NoSuchFlowModelException when the flow model with the specified id does not exist + * @throws FlowModelConstructionException if there is a problem constructing the identified flow model + */ + public FlowModel getFlowModel(String id) throws NoSuchFlowModelException, FlowModelConstructionException; +} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/FlowModelRegistry.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/FlowModelRegistry.java new file mode 100644 index 00000000..57c21f28 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/FlowModelRegistry.java @@ -0,0 +1,45 @@ +/* + * 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.model.registry; + +/** + * A container of flow models. Extends {@link FlowModelLocator} for accessing registered Flow models for conversion to + * flow definitions. + *

    + * Flow model registries can be configured with a "parent" registry to provide a hook into a larger flow model registry + * hierarchy. + * + * @author Keith Donald + * @author Scott Andrews + */ +public interface FlowModelRegistry extends FlowModelLocator { + + /** + * Sets this registry's parent registry. When asked by a client to locate a flow model this registry will query it's + * parent if it cannot fulfill the lookup request itself. + * @param parent the parent flow model registry, may be null + */ + public void setParent(FlowModelRegistry parent); + + /** + * Register a flow model in this registry. Registers a "holder", not the Flow model itself. This allows the actual + * Flow model to be loaded lazily only when needed, and also rebuilt at runtime when its underlying resource changes + * without re-deploy. + * @param modelHolder a holder holding the flow model to register + */ + public void registerFlowModel(FlowModelHolder modelHolder); + +} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/FlowModelRegistryImpl.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/FlowModelRegistryImpl.java new file mode 100644 index 00000000..285f5dfd --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/FlowModelRegistryImpl.java @@ -0,0 +1,102 @@ +/* + * 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.model.registry; + +import java.util.Map; +import java.util.TreeMap; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.style.ToStringCreator; +import org.springframework.util.Assert; +import org.springframework.webflow.engine.model.FlowModel; + +/** + * A generic registry implementation for housing one or more flow models. + * + * @author Keith Donald + * @author Scott Andrews + */ +public class FlowModelRegistryImpl implements FlowModelRegistry { + + private static final Log logger = LogFactory.getLog(FlowModelRegistryImpl.class); + + /** + * The map of loaded Flow models maintained in this registry. + */ + private Map flowModels; + + /** + * An optional parent flow model registry. + */ + private FlowModelRegistry parent; + + public FlowModelRegistryImpl() { + flowModels = new TreeMap(); + } + + // implementing FlowModelLocator + + public FlowModel getFlowModel(String id) throws NoSuchFlowModelException, FlowModelConstructionException { + try { + if (id == null) { + throw new IllegalArgumentException("The id of the flow to lookup is required"); + } + if (logger.isDebugEnabled()) { + logger.debug("Getting flow model with id '" + id + "'"); + } + return getFlowModelHolder(id).getFlowModel(); + + } catch (NoSuchFlowModelException e) { + if (parent != null) { + // try parent + return parent.getFlowModel(id); + } + throw e; + } + } + + // implementing FlowModelRegistry + + public void setParent(FlowModelRegistry parent) { + this.parent = parent; + } + + public void registerFlowModel(FlowModelHolder modelHolder) { + Assert.notNull(modelHolder, "The holder of the flow model to register is required"); + if (logger.isDebugEnabled()) { + logger.debug("Registering flow model " + modelHolder); + } + flowModels.put(modelHolder.getFlowModelId(), modelHolder); + } + + // internal helpers + + /** + * Returns the identified flow model holder. Throws an exception if it cannot be found. + */ + private FlowModelHolder getFlowModelHolder(String id) throws NoSuchFlowModelException { + FlowModelHolder holder = (FlowModelHolder) flowModels.get(id); + if (holder == null) { + throw new NoSuchFlowModelException(id); + } + return holder; + } + + public String toString() { + return new ToStringCreator(this).append("flowModels", flowModels).append("parent", parent).toString(); + } +} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/NoSuchFlowModelException.java b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/NoSuchFlowModelException.java new file mode 100644 index 00000000..0189ec68 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/engine/model/registry/NoSuchFlowModelException.java @@ -0,0 +1,49 @@ +/* + * 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.model.registry; + +import org.springframework.webflow.core.FlowException; + +/** + * Thrown when no flow model was found during a lookup operation by a flow locator. + * + * @author Keith Donald + * @author Erwin Vervaet + * @author Scott Andrews + */ +public class NoSuchFlowModelException extends FlowException { + + /** + * The id of the flow model that could not be located. + */ + private String flowModelId; + + /** + * Creates an exception indicating a flow model could not be found. + * @param flowModelId the flow model id + */ + public NoSuchFlowModelException(String flowModelId) { + super("No flow model '" + flowModelId + "' found"); + this.flowModelId = flowModelId; + } + + /** + * Returns the id of the flow model that could not be found. + */ + public String getFlowModelId() { + return flowModelId; + } +} \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/test/execution/AbstractExternalizedFlowExecutionTests.java b/spring-webflow/src/main/java/org/springframework/webflow/test/execution/AbstractExternalizedFlowExecutionTests.java index 0c602bed..b0fa4b49 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/test/execution/AbstractExternalizedFlowExecutionTests.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/test/execution/AbstractExternalizedFlowExecutionTests.java @@ -15,7 +15,6 @@ */ package org.springframework.webflow.test.execution; -import org.springframework.core.io.Resource; import org.springframework.webflow.config.FlowDefinitionResource; import org.springframework.webflow.config.FlowDefinitionResourceFactory; import org.springframework.webflow.core.collection.AttributeMap; @@ -33,6 +32,7 @@ import org.springframework.webflow.test.MockFlowBuilderContext; * caching of the flow definition built from an externalized resource to speed up test execution. * * @author Keith Donald + * @author Scott Andrews */ public abstract class AbstractExternalizedFlowExecutionTests extends AbstractFlowExecutionTests { @@ -145,7 +145,7 @@ public abstract class AbstractExternalizedFlowExecutionTests extends AbstractFlo FlowDefinitionResource resource = getResource(resourceFactory); MockFlowBuilderContext builderContext = new MockFlowBuilderContext(resource.getId(), resource.getAttributes()); configureFlowBuilderContext(builderContext); - FlowBuilder builder = createFlowBuilder(resource.getPath()); + FlowBuilder builder = createFlowBuilder(resource); FlowAssembler assembler = new FlowAssembler(builder, builderContext); return assembler.assembleFlow(); } @@ -168,9 +168,9 @@ public abstract class AbstractExternalizedFlowExecutionTests extends AbstractFlo /** * Create the flow builder to build the flow at the specified resource location. - * @param path the location of the flow definition + * @param resource the resource location of the flow definition * @return the flow builder that can build the flow definition */ - protected abstract FlowBuilder createFlowBuilder(Resource path); + protected abstract FlowBuilder createFlowBuilder(FlowDefinitionResource resource); } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/test/execution/AbstractXmlFlowExecutionTests.java b/spring-webflow/src/main/java/org/springframework/webflow/test/execution/AbstractXmlFlowExecutionTests.java index 576d3f94..6ad0673f 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/test/execution/AbstractXmlFlowExecutionTests.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/test/execution/AbstractXmlFlowExecutionTests.java @@ -16,9 +16,15 @@ package org.springframework.webflow.test.execution; import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.core.io.Resource; +import org.springframework.webflow.config.FlowDefinitionResource; import org.springframework.webflow.engine.builder.FlowBuilder; -import org.springframework.webflow.engine.builder.xml.XmlFlowBuilder; +import org.springframework.webflow.engine.builder.FlowModelFlowBuilder; +import org.springframework.webflow.engine.model.builder.FlowModelBuilder; +import org.springframework.webflow.engine.model.builder.xml.XmlFlowModelBuilder; +import org.springframework.webflow.engine.model.registry.DefaultFlowModelHolder; +import org.springframework.webflow.engine.model.registry.FlowModelHolder; +import org.springframework.webflow.engine.model.registry.FlowModelRegistry; +import org.springframework.webflow.engine.model.registry.FlowModelRegistryImpl; /** * Base class for flow integration tests that verify an XML flow definition executes as expected. @@ -47,15 +53,19 @@ import org.springframework.webflow.engine.builder.xml.XmlFlowBuilder; * * @author Keith Donald * @author Erwin Vervaet + * @author Scott Andrews */ public abstract class AbstractXmlFlowExecutionTests extends AbstractExternalizedFlowExecutionTests { + private FlowModelRegistry flowModelRegistry; + /** * Constructs a default XML flow execution test. * @see #setName(String) */ public AbstractXmlFlowExecutionTests() { super(); + flowModelRegistry = new FlowModelRegistryImpl(); } /** @@ -64,10 +74,14 @@ public abstract class AbstractXmlFlowExecutionTests extends AbstractExternalized */ public AbstractXmlFlowExecutionTests(String name) { super(name); + flowModelRegistry = new FlowModelRegistryImpl(); } - protected FlowBuilder createFlowBuilder(Resource resource) { - return new XmlFlowBuilder(resource) { + protected FlowBuilder createFlowBuilder(FlowDefinitionResource resource) { + FlowModelBuilder modelBuilder = new XmlFlowModelBuilder(resource.getPath(), flowModelRegistry); + FlowModelHolder modelHolder = new DefaultFlowModelHolder(modelBuilder, resource.getId()); + flowModelRegistry.registerFlowModel(modelHolder); + return new FlowModelFlowBuilder(modelHolder, resource.getPath()) { protected void registerFlowBeans(ConfigurableBeanFactory flowBeanFactory) { registerMockFlowBeans(flowBeanFactory); } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/FlowModelFlowBuilderTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/FlowModelFlowBuilderTests.java new file mode 100644 index 00000000..f40c7cc2 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/FlowModelFlowBuilderTests.java @@ -0,0 +1,299 @@ +package org.springframework.webflow.engine.builder; + +import junit.framework.TestCase; + +import org.springframework.beans.factory.support.StaticListableBeanFactory; +import org.springframework.core.io.ClassPathResource; +import org.springframework.webflow.action.ExternalRedirectAction; +import org.springframework.webflow.action.FlowDefinitionRedirectAction; +import org.springframework.webflow.core.collection.LocalAttributeMap; +import org.springframework.webflow.core.collection.MutableAttributeMap; +import org.springframework.webflow.engine.Flow; +import org.springframework.webflow.engine.FlowInputMappingException; +import org.springframework.webflow.engine.FlowOutputMappingException; +import org.springframework.webflow.engine.ViewState; +import org.springframework.webflow.engine.builder.support.ActionExecutingViewFactory; +import org.springframework.webflow.engine.impl.FlowExecutionImplFactory; +import org.springframework.webflow.engine.model.AttributeModel; +import org.springframework.webflow.engine.model.EndStateModel; +import org.springframework.webflow.engine.model.FlowModel; +import org.springframework.webflow.engine.model.InputModel; +import org.springframework.webflow.engine.model.OutputModel; +import org.springframework.webflow.engine.model.PersistenceContextModel; +import org.springframework.webflow.engine.model.SecuredModel; +import org.springframework.webflow.engine.model.TransitionModel; +import org.springframework.webflow.engine.model.VarModel; +import org.springframework.webflow.engine.model.ViewStateModel; +import org.springframework.webflow.engine.model.builder.xml.XmlFlowModelBuilder; +import org.springframework.webflow.engine.model.builder.xml.XmlFlowModelBuilderTests; +import org.springframework.webflow.engine.model.registry.DefaultFlowModelHolder; +import org.springframework.webflow.engine.model.registry.FlowModelHolder; +import org.springframework.webflow.engine.model.registry.FlowModelRegistryImpl; +import org.springframework.webflow.execution.Event; +import org.springframework.webflow.execution.FlowExecution; +import org.springframework.webflow.execution.ViewFactory; +import org.springframework.webflow.security.SecurityRule; +import org.springframework.webflow.test.MockExternalContext; +import org.springframework.webflow.test.MockFlowBuilderContext; + +public class FlowModelFlowBuilderTests extends TestCase { + private FlowModel model; + + protected void setUp() { + StaticListableBeanFactory beanFactory = new StaticListableBeanFactory(); + beanFactory.addBean("bean", new Object()); + model = new FlowModel(); + } + + public void testBuildIncompleteFlow() { + try { + getFlow(model); + fail("Should have failed"); + } catch (FlowBuilderException e) { + } + } + + public void testBuildFlowWithEndState() { + model.addEndState(new EndStateModel("end")); + Flow flow = getFlow(model); + assertEquals("flow", flow.getId()); + assertEquals("end", flow.getStartState().getId()); + } + + public void testBuildFlowWithDefaultStartState() { + model.addEndState(new EndStateModel("end")); + Flow flow = getFlow(model); + assertEquals("flow", flow.getId()); + assertEquals("end", flow.getStartState().getId()); + } + + public void testBuildFlowWithStartStateAttribute() { + model.setStartStateId("end"); + model.addEndState(new EndStateModel("foo")); + model.addEndState(new EndStateModel("end")); + Flow flow = getFlow(model); + assertEquals("flow", flow.getId()); + assertEquals("end", flow.getStartState().getId()); + } + + public void testCustomFlowAttribute() { + model.addAttribute(new AttributeModel("foo", "bar")); + model.addAttribute(new AttributeModel("number", "1", "integer")); + model.addEndState(new EndStateModel("end")); + Flow flow = getFlow(model); + assertEquals("bar", flow.getAttributes().get("foo")); + assertEquals(new Integer(1), flow.getAttributes().get("number")); + } + + public void testPersistenceContextFlow() { + model.setPersistenceContext(new PersistenceContextModel()); + model.addEndState(new EndStateModel("end")); + Flow flow = getFlow(model); + assertNotNull(flow.getAttributes().get("persistenceContext")); + assertTrue(((Boolean) flow.getAttributes().get("persistenceContext")).booleanValue()); + } + + public void testFlowInputOutputMapping() { + model.addInput(new InputModel("foo", "flowScope.foo")); + model.addInput(new InputModel("foo", "flowScope.bar")); + model.addInput(new InputModel("number", "flowScope.baz", "integer", null)); + model.addInput(new InputModel("required", "flowScope.boop", null, "true")); + EndStateModel end = new EndStateModel("end"); + end.addOutput(new OutputModel("foo", "flowScope.foo")); + model.addEndState(end); + EndStateModel notReached = new EndStateModel("notReached"); + notReached.addOutput(new OutputModel("notReached", "flowScope.foo")); + model.addEndState(notReached); + model.addOutput(new OutputModel("differentName", "flowScope.bar")); + model.addOutput(new OutputModel("number", "flowScope.baz", "integer", null)); + model.addOutput(new OutputModel("required", "flowScope.baz", "integer", "true")); + model.addOutput(new OutputModel("literal", "'a literal'")); + Flow flow = getFlow(model); + FlowExecutionImplFactory factory = new FlowExecutionImplFactory(); + FlowExecution execution = factory.createFlowExecution(flow); + MockExternalContext context = new MockExternalContext(); + MutableAttributeMap input = new LocalAttributeMap(); + input.put("foo", "bar"); + input.put("number", "3"); + input.put("required", "9"); + execution.start(input, context); + Event outcome = execution.getOutcome(); + assertEquals("end", outcome.getId()); + assertEquals("bar", outcome.getAttributes().get("foo")); + assertEquals("bar", outcome.getAttributes().get("differentName")); + assertEquals(new Integer(3), outcome.getAttributes().get("number")); + assertEquals(new Integer(3), outcome.getAttributes().get("required")); + assertEquals("a literal", outcome.getAttributes().get("literal")); + assertNull(outcome.getAttributes().get("notReached")); + } + + public void testFlowRequiredInputMapping() { + model.addInput(new InputModel("foo", "flowScope.foo")); + model.addInput(new InputModel("foo", "flowScope.bar")); + model.addInput(new InputModel("number", "flowScope.baz", "integer", null)); + model.addInput(new InputModel("required", "flowScope.boop", null, "true")); + EndStateModel end = new EndStateModel("end"); + end.addOutput(new OutputModel("foo", "flowScope.foo")); + model.addEndState(end); + EndStateModel notReached = new EndStateModel("notReached"); + notReached.addOutput(new OutputModel("notReached", "flowScope.foo")); + model.addEndState(notReached); + model.addOutput(new OutputModel("differentName", "flowScope.bar")); + model.addOutput(new OutputModel("number", "flowScope.baz", "integer", null)); + model.addOutput(new OutputModel("required", "flowScope.baz", "integer", "true")); + model.addOutput(new OutputModel("literal", "'a literal'")); + Flow flow = getFlow(model); + FlowExecutionImplFactory factory = new FlowExecutionImplFactory(); + FlowExecution execution = factory.createFlowExecution(flow); + MockExternalContext context = new MockExternalContext(); + MutableAttributeMap input = new LocalAttributeMap(); + try { + execution.start(input, context); + fail("Should have failed"); + } catch (FlowInputMappingException e) { + } + } + + public void testFlowRequiredOutputMapping() { + model.addInput(new InputModel("foo", "flowScope.foo")); + model.addInput(new InputModel("foo", "flowScope.bar")); + model.addInput(new InputModel("number", "flowScope.baz", "integer", null)); + model.addInput(new InputModel("required", "flowScope.boop", null, "true")); + EndStateModel end = new EndStateModel("end"); + end.addOutput(new OutputModel("foo", "flowScope.foo")); + model.addEndState(end); + EndStateModel notReached = new EndStateModel("notReached"); + notReached.addOutput(new OutputModel("notReached", "flowScope.foo")); + model.addEndState(notReached); + model.addOutput(new OutputModel("differentName", "flowScope.bar")); + model.addOutput(new OutputModel("number", "flowScope.baz", "integer", null)); + model.addOutput(new OutputModel("required", "flowScope.baz", "integer", "true")); + model.addOutput(new OutputModel("literal", "'a literal'")); + Flow flow = getFlow(model); + FlowExecutionImplFactory factory = new FlowExecutionImplFactory(); + FlowExecution execution = factory.createFlowExecution(flow); + MockExternalContext context = new MockExternalContext(); + MutableAttributeMap input = new LocalAttributeMap(); + input.put("required", "yo"); + try { + execution.start(input, context); + fail("Should have failed"); + } catch (FlowOutputMappingException e) { + } + } + + public void testFlowSecured() { + model.setSecured(new SecuredModel("ROLE_USER")); + model.addEndState(new EndStateModel("end")); + Flow flow = getFlow(model); + SecurityRule rule = (SecurityRule) flow.getAttributes().get(SecurityRule.SECURITY_ATTRIBUTE_NAME); + assertNotNull(rule); + assertEquals(SecurityRule.COMPARISON_ANY, rule.getComparisonType()); + assertEquals(1, rule.getAttributes().size()); + assertTrue(rule.getAttributes().contains("ROLE_USER")); + } + + public void testFlowSecuredState() { + EndStateModel end = new EndStateModel("end"); + end.setSecured(new SecuredModel("ROLE_USER")); + model.addEndState(end); + Flow flow = getFlow(model); + SecurityRule rule = (SecurityRule) flow.getState("end").getAttributes().get( + SecurityRule.SECURITY_ATTRIBUTE_NAME); + assertNotNull(rule); + assertEquals(SecurityRule.COMPARISON_ANY, rule.getComparisonType()); + assertEquals(1, rule.getAttributes().size()); + assertTrue(rule.getAttributes().contains("ROLE_USER")); + } + + public void testFlowSecuredTransition() { + model.addEndState(new EndStateModel("end")); + TransitionModel transition = new TransitionModel(null, "end"); + transition.setSecured(new SecuredModel("ROLE_USER")); + model.addGlobalTransition(transition); + Flow flow = getFlow(model); + SecurityRule rule = (SecurityRule) flow.getGlobalTransitionSet().toArray()[0].getAttributes().get( + SecurityRule.SECURITY_ATTRIBUTE_NAME); + assertNotNull(rule); + assertEquals(SecurityRule.COMPARISON_ANY, rule.getComparisonType()); + assertEquals(1, rule.getAttributes().size()); + assertTrue(rule.getAttributes().contains("ROLE_USER")); + } + + public void testFlowVariable() { + model.addVar(new VarModel("flow-foo", "org.springframework.webflow.TestBean")); + model.addVar(new VarModel("conversation-foo", "org.springframework.webflow.TestBean", "conversation")); + model.addEndState(new EndStateModel("end")); + Flow flow = getFlow(model); + assertEquals("flow-foo", flow.getVariable("flow-foo").getName()); + assertEquals(true, flow.getVariable("flow-foo").isLocal()); + assertEquals("conversation-foo", flow.getVariables()[1].getName()); + assertEquals(false, flow.getVariables()[1].isLocal()); + } + + public void testViewStateVariable() { + ViewStateModel view = new ViewStateModel("view"); + view.addVar(new VarModel("foo", "org.springframework.webflow.TestBean")); + model.addViewState(view); + Flow flow = getFlow(model); + assertNotNull(((ViewState) flow.getStateInstance("view")).getVariable("foo")); + } + + public void testViewStateRedirect() { + ViewStateModel view = new ViewStateModel("view"); + view.setRedirect("true"); + model.addViewState(view); + Flow flow = getFlow(model); + assertTrue(((ViewState) flow.getStateInstance("view")).getRedirect()); + } + + public void testViewStatePopup() { + ViewStateModel view = new ViewStateModel("view"); + view.setPopup("true"); + model.addViewState(view); + Flow flow = getFlow(model); + assertTrue(((ViewState) flow.getStateInstance("view")).getPopup()); + } + + public void testViewStateFlowRedirect() { + model.addViewState(new ViewStateModel("view", "flowRedirect:myFlow?input=#{flowScope.foo}")); + Flow flow = getFlow(model); + ViewFactory vf = ((ViewState) flow.getStateInstance("view")).getViewFactory(); + assertTrue(vf instanceof ActionExecutingViewFactory); + ActionExecutingViewFactory avf = (ActionExecutingViewFactory) vf; + assertTrue(avf.getAction() instanceof FlowDefinitionRedirectAction); + } + + public void testViewStateExternalRedirect() { + model.addViewState(new ViewStateModel("view", + "externalRedirect:http://www.paypal.com?_callbackUrl=#{flowExecutionUri}")); + Flow flow = getFlow(model); + ViewFactory vf = ((ViewState) flow.getStateInstance("view")).getViewFactory(); + assertTrue(vf instanceof ActionExecutingViewFactory); + ActionExecutingViewFactory avf = (ActionExecutingViewFactory) vf; + assertTrue(avf.getAction() instanceof ExternalRedirectAction); + } + + public void testResourceBackedFlowBuilder() { + ClassPathResource resource = new ClassPathResource("flow-endstate.xml", XmlFlowModelBuilderTests.class); + Flow flow = getFlow(resource); + assertEquals("flow", flow.getId()); + assertEquals("end", flow.getStartState().getId()); + } + + private Flow getFlow(FlowModel model) { + FlowModelHolder holder = new DefaultFlowModelHolder(model, "flow"); + FlowModelFlowBuilder builder = new FlowModelFlowBuilder(holder); + FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow")); + return assembler.assembleFlow(); + } + + private Flow getFlow(ClassPathResource resource) { + FlowModelHolder holder = new DefaultFlowModelHolder(new XmlFlowModelBuilder(resource, + new FlowModelRegistryImpl()), "flow"); + FlowModelFlowBuilder builder = new FlowModelFlowBuilder(holder, resource); + FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow")); + return assembler.assembleFlow(); + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilderTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilderTests.java deleted file mode 100644 index 8fb94275..00000000 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/XmlFlowBuilderTests.java +++ /dev/null @@ -1,241 +0,0 @@ -package org.springframework.webflow.engine.builder.xml; - -import junit.framework.TestCase; - -import org.springframework.beans.factory.support.StaticListableBeanFactory; -import org.springframework.core.io.ClassPathResource; -import org.springframework.webflow.action.ExternalRedirectAction; -import org.springframework.webflow.action.FlowDefinitionRedirectAction; -import org.springframework.webflow.core.collection.LocalAttributeMap; -import org.springframework.webflow.core.collection.MutableAttributeMap; -import org.springframework.webflow.engine.Flow; -import org.springframework.webflow.engine.FlowInputMappingException; -import org.springframework.webflow.engine.FlowOutputMappingException; -import org.springframework.webflow.engine.ViewState; -import org.springframework.webflow.engine.builder.FlowAssembler; -import org.springframework.webflow.engine.builder.FlowBuilderException; -import org.springframework.webflow.engine.builder.support.ActionExecutingViewFactory; -import org.springframework.webflow.engine.impl.FlowExecutionImplFactory; -import org.springframework.webflow.execution.Event; -import org.springframework.webflow.execution.FlowExecution; -import org.springframework.webflow.execution.ViewFactory; -import org.springframework.webflow.security.SecurityRule; -import org.springframework.webflow.test.MockExternalContext; -import org.springframework.webflow.test.MockFlowBuilderContext; - -public class XmlFlowBuilderTests extends TestCase { - private XmlFlowBuilder builder; - - protected void setUp() { - StaticListableBeanFactory beanFactory = new StaticListableBeanFactory(); - beanFactory.addBean("bean", new Object()); - } - - public void testBuildIncompleteFlow() { - ClassPathResource resource = new ClassPathResource("flow-incomplete.xml", getClass()); - builder = new XmlFlowBuilder(resource); - FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow")); - try { - assembler.assembleFlow(); - fail("Should have failed"); - } catch (FlowBuilderException e) { - } - } - - public void testBuildFlowWithEndState() { - ClassPathResource resource = new ClassPathResource("flow-endstate.xml", getClass()); - builder = new XmlFlowBuilder(resource); - FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow")); - Flow flow = assembler.assembleFlow(); - assertEquals("flow", flow.getId()); - assertEquals("end", flow.getStartState().getId()); - } - - public void testBuildFlowWithDefaultStartState() { - ClassPathResource resource = new ClassPathResource("flow-startstate-default.xml", getClass()); - builder = new XmlFlowBuilder(resource); - FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow")); - Flow flow = assembler.assembleFlow(); - assertEquals("flow", flow.getId()); - assertEquals("end", flow.getStartState().getId()); - } - - public void testBuildFlowWithStartStateAttribute() { - ClassPathResource resource = new ClassPathResource("flow-startstate-attribute.xml", getClass()); - builder = new XmlFlowBuilder(resource); - FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow")); - Flow flow = assembler.assembleFlow(); - assertEquals("flow", flow.getId()); - assertEquals("end", flow.getStartState().getId()); - } - - public void testCustomFlowAttribute() { - ClassPathResource resource = new ClassPathResource("flow-custom-attribute.xml", getClass()); - builder = new XmlFlowBuilder(resource); - FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow")); - Flow flow = assembler.assembleFlow(); - assertEquals("bar", flow.getAttributes().get("foo")); - assertEquals(new Integer(1), flow.getAttributes().get("number")); - } - - public void testPersistenceContextFlow() { - ClassPathResource resource = new ClassPathResource("flow-persistencecontext.xml", getClass()); - builder = new XmlFlowBuilder(resource); - FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow")); - Flow flow = assembler.assembleFlow(); - assertNotNull(flow.getAttributes().get("persistenceContext")); - assertTrue(((Boolean) flow.getAttributes().get("persistenceContext")).booleanValue()); - } - - public void testFlowInputOutputMapping() { - ClassPathResource resource = new ClassPathResource("flow-inputoutput.xml", getClass()); - builder = new XmlFlowBuilder(resource); - FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow")); - Flow flow = assembler.assembleFlow(); - FlowExecutionImplFactory factory = new FlowExecutionImplFactory(); - FlowExecution execution = factory.createFlowExecution(flow); - MockExternalContext context = new MockExternalContext(); - MutableAttributeMap input = new LocalAttributeMap(); - input.put("foo", "bar"); - input.put("number", "3"); - input.put("required", "9"); - execution.start(input, context); - Event outcome = execution.getOutcome(); - assertEquals("end", outcome.getId()); - assertEquals("bar", outcome.getAttributes().get("foo")); - assertEquals("bar", outcome.getAttributes().get("differentName")); - assertEquals(new Integer(3), outcome.getAttributes().get("number")); - assertEquals(new Integer(3), outcome.getAttributes().get("required")); - assertEquals("a literal", outcome.getAttributes().get("literal")); - assertNull(outcome.getAttributes().get("notReached")); - } - - public void testFlowRequiredInputMapping() { - ClassPathResource resource = new ClassPathResource("flow-inputoutput.xml", getClass()); - builder = new XmlFlowBuilder(resource); - FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow")); - Flow flow = assembler.assembleFlow(); - FlowExecutionImplFactory factory = new FlowExecutionImplFactory(); - FlowExecution execution = factory.createFlowExecution(flow); - MockExternalContext context = new MockExternalContext(); - MutableAttributeMap input = new LocalAttributeMap(); - try { - execution.start(input, context); - fail("Should have failed"); - } catch (FlowInputMappingException e) { - } - } - - public void testFlowRequiredOutputMapping() { - ClassPathResource resource = new ClassPathResource("flow-inputoutput.xml", getClass()); - builder = new XmlFlowBuilder(resource); - FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow")); - Flow flow = assembler.assembleFlow(); - FlowExecutionImplFactory factory = new FlowExecutionImplFactory(); - FlowExecution execution = factory.createFlowExecution(flow); - MockExternalContext context = new MockExternalContext(); - MutableAttributeMap input = new LocalAttributeMap(); - input.put("required", "yo"); - try { - execution.start(input, context); - fail("Should have failed"); - } catch (FlowOutputMappingException e) { - } - } - - public void testFlowSecured() { - ClassPathResource resource = new ClassPathResource("flow-secured.xml", getClass()); - builder = new XmlFlowBuilder(resource); - FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow")); - Flow flow = assembler.assembleFlow(); - SecurityRule rule = (SecurityRule) flow.getAttributes().get(SecurityRule.SECURITY_ATTRIBUTE_NAME); - assertNotNull(rule); - assertEquals(SecurityRule.COMPARISON_ANY, rule.getComparisonType()); - assertEquals(1, rule.getAttributes().size()); - assertTrue(rule.getAttributes().contains("ROLE_USER")); - } - - public void testFlowSecuredState() { - ClassPathResource resource = new ClassPathResource("flow-secured-state.xml", getClass()); - builder = new XmlFlowBuilder(resource); - FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow")); - Flow flow = assembler.assembleFlow(); - SecurityRule rule = (SecurityRule) flow.getState("end").getAttributes().get( - SecurityRule.SECURITY_ATTRIBUTE_NAME); - assertNotNull(rule); - assertEquals(SecurityRule.COMPARISON_ANY, rule.getComparisonType()); - assertEquals(1, rule.getAttributes().size()); - assertTrue(rule.getAttributes().contains("ROLE_USER")); - } - - public void testFlowSecuredTransition() { - ClassPathResource resource = new ClassPathResource("flow-secured-transition.xml", getClass()); - builder = new XmlFlowBuilder(resource); - FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow")); - Flow flow = assembler.assembleFlow(); - SecurityRule rule = (SecurityRule) flow.getGlobalTransitionSet().toArray()[0].getAttributes().get( - SecurityRule.SECURITY_ATTRIBUTE_NAME); - assertNotNull(rule); - assertEquals(SecurityRule.COMPARISON_ANY, rule.getComparisonType()); - assertEquals(1, rule.getAttributes().size()); - assertTrue(rule.getAttributes().contains("ROLE_USER")); - } - - public void testFlowVariable() { - ClassPathResource resource = new ClassPathResource("flow-var.xml", getClass()); - builder = new XmlFlowBuilder(resource); - FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow")); - Flow flow = assembler.assembleFlow(); - assertEquals("flow-foo", flow.getVariable("flow-foo").getName()); - assertEquals(true, flow.getVariable("flow-foo").isLocal()); - assertEquals("conversation-foo", flow.getVariables()[1].getName()); - assertEquals(false, flow.getVariables()[1].isLocal()); - } - - public void testViewStateVariable() { - ClassPathResource resource = new ClassPathResource("flow-viewstate-var.xml", getClass()); - builder = new XmlFlowBuilder(resource); - FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow")); - Flow flow = assembler.assembleFlow(); - assertNotNull(((ViewState) flow.getStateInstance("view")).getVariable("foo")); - } - - public void testViewStateRedirect() { - ClassPathResource resource = new ClassPathResource("flow-viewstate-redirect.xml", getClass()); - builder = new XmlFlowBuilder(resource); - FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow")); - Flow flow = assembler.assembleFlow(); - assertTrue(((ViewState) flow.getStateInstance("view")).getRedirect()); - } - - public void testViewStatePopup() { - ClassPathResource resource = new ClassPathResource("flow-viewstate-popup.xml", getClass()); - builder = new XmlFlowBuilder(resource); - FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow")); - Flow flow = assembler.assembleFlow(); - assertTrue(((ViewState) flow.getStateInstance("view")).getPopup()); - } - - public void testViewStateFlowRedirect() { - ClassPathResource resource = new ClassPathResource("flow-viewstate-flowredirect.xml", getClass()); - builder = new XmlFlowBuilder(resource); - FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow")); - Flow flow = assembler.assembleFlow(); - ViewFactory vf = ((ViewState) flow.getStateInstance("view")).getViewFactory(); - assertTrue(vf instanceof ActionExecutingViewFactory); - ActionExecutingViewFactory avf = (ActionExecutingViewFactory) vf; - assertTrue(avf.getAction() instanceof FlowDefinitionRedirectAction); - } - - public void testViewStateExternalRedirect() { - ClassPathResource resource = new ClassPathResource("flow-viewstate-externalredirect.xml", getClass()); - builder = new XmlFlowBuilder(resource); - FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow")); - Flow flow = assembler.assembleFlow(); - ViewFactory vf = ((ViewState) flow.getStateInstance("view")).getViewFactory(); - assertTrue(vf instanceof ActionExecutingViewFactory); - ActionExecutingViewFactory avf = (ActionExecutingViewFactory) vf; - assertTrue(avf.getAction() instanceof ExternalRedirectAction); - } - -} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/AbstractModelTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/AbstractModelTests.java new file mode 100644 index 00000000..a86519ec --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/AbstractModelTests.java @@ -0,0 +1,94 @@ +/* + * 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.model; + +import java.util.LinkedList; + +import junit.framework.TestCase; + +/** + * Unit tests for {@link AbstractModel}. + */ +public class AbstractModelTests extends TestCase { + + public void testStringMerge() { + AbstractModel obj = new PersistenceContextModel(); + String child = "child"; + String parent = "parent"; + assertEquals("child", obj.merge(child, parent)); + } + + public void testStringMergeNullParent() { + AbstractModel obj = new PersistenceContextModel(); + String child = "child"; + String parent = null; + assertEquals("child", obj.merge(child, parent)); + } + + public void testStringMergeNullChild() { + AbstractModel obj = new PersistenceContextModel(); + String child = null; + String parent = "parent"; + assertEquals("parent", obj.merge(child, parent)); + } + + public void testStringMergeNulls() { + AbstractModel obj = new PersistenceContextModel(); + String child = null; + String parent = null; + assertEquals(null, obj.merge(child, parent)); + } + + public void testListMerge() { + AbstractModel obj = new PersistenceContextModel(); + LinkedList child = new LinkedList(); + child.add(new SecuredModel("1")); + LinkedList parent = new LinkedList(); + parent.add(new SecuredModel("2")); + LinkedList result = obj.merge(child, parent); + assertEquals(2, result.size()); + assertEquals("1", ((SecuredModel) result.get(0)).getAttributes()); + assertEquals("2", ((SecuredModel) result.get(1)).getAttributes()); + } + + public void testListMergeNullParent() { + AbstractModel obj = new PersistenceContextModel(); + LinkedList child = new LinkedList(); + child.add("1"); + LinkedList parent = null; + LinkedList result = obj.merge(child, parent); + assertEquals(1, result.size()); + assertEquals("1", result.get(0)); + } + + public void testListMergeNullChild() { + AbstractModel obj = new PersistenceContextModel(); + LinkedList child = null; + LinkedList parent = new LinkedList(); + parent.add("2"); + LinkedList result = obj.merge(child, parent); + assertEquals(1, result.size()); + assertEquals("2", result.get(0)); + } + + public void testListMergeNulls() { + AbstractModel obj = new PersistenceContextModel(); + LinkedList child = null; + LinkedList parent = null; + LinkedList result = obj.merge(child, parent); + assertEquals(null, result); + } +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/ActionStateModelTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/ActionStateModelTests.java new file mode 100644 index 00000000..7c0deacc --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/ActionStateModelTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2004-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.webflow.engine.model; + +import org.springframework.webflow.engine.model.ActionStateModel; +import org.springframework.webflow.engine.model.EvaluateModel; + +import junit.framework.TestCase; + +/** + * Unit tests for {@link ActionStateModel}. + */ +public class ActionStateModelTests extends TestCase { + + public void testMerge() { + ActionStateModel child = new ActionStateModel("child"); + ActionStateModel parent = new ActionStateModel("parent"); + child.merge(parent); + assertEquals("child", child.getId()); + } + + public void testMergeNullParent() { + ActionStateModel child = new ActionStateModel("child"); + ActionStateModel parent = null; + child.merge(parent); + assertEquals("child", child.getId()); + } + + public void testMergeOverrideMatch() { + ActionStateModel child = new ActionStateModel("child"); + ActionStateModel parent = new ActionStateModel("child"); + parent.addAction(new EvaluateModel("eval1")); + child.merge(parent); + assertEquals(1, child.getActions().size()); + } + + public void testMergeOverrideMatchFailed() { + ActionStateModel child = new ActionStateModel("child"); + ActionStateModel parent = new ActionStateModel("parent"); + parent.addAction(new EvaluateModel("eval1")); + child.merge(parent); + assertEquals(null, child.getActions()); + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/AttributeModelTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/AttributeModelTests.java new file mode 100644 index 00000000..179dd9be --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/AttributeModelTests.java @@ -0,0 +1,55 @@ +/* + * 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.model; + +import org.springframework.webflow.engine.model.AttributeModel; + +import junit.framework.TestCase; + +/** + * Unit tests for {@link AttributeModel}. + */ +public class AttributeModelTests extends TestCase { + + public void testMerge() { + AttributeModel child = new AttributeModel("child", "childvalue"); + AttributeModel parent = new AttributeModel("parent", "parentvalue"); + child.merge(parent); + assertEquals("child", child.getName()); + } + + public void testMergeNullParent() { + AttributeModel child = new AttributeModel("child", "childvalue"); + AttributeModel parent = null; + child.merge(parent); + assertEquals("child", child.getName()); + } + + public void testMergeOverrideMatch() { + AttributeModel child = new AttributeModel("child", "childvalue"); + AttributeModel parent = new AttributeModel("child", "childvalue", "string"); + child.merge(parent); + assertEquals("string", child.getType()); + } + + public void testMergeOverrideMatchFailed() { + AttributeModel child = new AttributeModel("child", "childvalue"); + AttributeModel parent = new AttributeModel("parent", "parentvalue", "string"); + child.merge(parent); + assertEquals(null, child.getType()); + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/BeanImportModelTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/BeanImportModelTests.java new file mode 100644 index 00000000..784a6296 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/BeanImportModelTests.java @@ -0,0 +1,49 @@ +/* + * 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.model; + +import org.springframework.webflow.engine.model.BeanImportModel; + +import junit.framework.TestCase; + +/** + * Unit tests for {@link BeanImportModel}. + */ +public class BeanImportModelTests extends TestCase { + + public void testMerge() { + BeanImportModel child = new BeanImportModel("child"); + BeanImportModel parent = new BeanImportModel("parent"); + child.merge(parent); + assertEquals("child", child.getResource()); + } + + public void testMergeNullParent() { + BeanImportModel child = new BeanImportModel("child"); + BeanImportModel parent = null; + child.merge(parent); + assertEquals("child", child.getResource()); + } + + public void testMergeOverrideMatch() { + // bean import will never merge + } + + public void testMergeOverrideMatchFailed() { + // bean import will never merge + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/DecisionStateModelTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/DecisionStateModelTests.java new file mode 100644 index 00000000..a191c460 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/DecisionStateModelTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2004-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.webflow.engine.model; + +import org.springframework.webflow.engine.model.DecisionStateModel; +import org.springframework.webflow.engine.model.IfModel; + +import junit.framework.TestCase; + +/** + * Unit tests for {@link DecisionStateModel}. + */ +public class DecisionStateModelTests extends TestCase { + + public void testMerge() { + DecisionStateModel child = new DecisionStateModel("child"); + DecisionStateModel parent = new DecisionStateModel("parent"); + child.merge(parent); + assertEquals("child", child.getId()); + } + + public void testMergeNullParent() { + DecisionStateModel child = new DecisionStateModel("child"); + DecisionStateModel parent = null; + child.merge(parent); + assertEquals("child", child.getId()); + } + + public void testMergeOverrideMatch() { + DecisionStateModel child = new DecisionStateModel("child"); + DecisionStateModel parent = new DecisionStateModel("child"); + parent.addIf(new IfModel("test", "then")); + child.merge(parent); + assertEquals(1, child.getIfs().size()); + } + + public void testMergeOverrideMatchFailed() { + DecisionStateModel child = new DecisionStateModel("child"); + DecisionStateModel parent = new DecisionStateModel("parent"); + parent.addIf(new IfModel("test", "then")); + child.merge(parent); + assertEquals(null, child.getIfs()); + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/EndStateModelTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/EndStateModelTests.java new file mode 100644 index 00000000..eeeb52bf --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/EndStateModelTests.java @@ -0,0 +1,57 @@ +/* + * 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.model; + +import org.springframework.webflow.engine.model.EndStateModel; + +import junit.framework.TestCase; + +/** + * Unit tests for {@link EndStateModel}. + */ +public class EndStateModelTests extends TestCase { + + public void testMerge() { + EndStateModel child = new EndStateModel("child"); + EndStateModel parent = new EndStateModel("parent"); + child.merge(parent); + assertEquals("child", child.getId()); + } + + public void testMergeNullParent() { + EndStateModel child = new EndStateModel("child"); + EndStateModel parent = null; + child.merge(parent); + assertEquals("child", child.getId()); + } + + public void testMergeOverrideMatch() { + EndStateModel child = new EndStateModel("child"); + EndStateModel parent = new EndStateModel("child"); + parent.setCommit("true"); + child.merge(parent); + assertEquals("true", child.getCommit()); + } + + public void testMergeOverrideMatchFailed() { + EndStateModel child = new EndStateModel("child"); + EndStateModel parent = new EndStateModel("parent"); + parent.setCommit("true"); + child.merge(parent); + assertEquals(null, child.getCommit()); + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/EvaluateModelTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/EvaluateModelTests.java new file mode 100644 index 00000000..8c8ef6c0 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/EvaluateModelTests.java @@ -0,0 +1,55 @@ +/* + * 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.model; + +import org.springframework.webflow.engine.model.EvaluateModel; + +import junit.framework.TestCase; + +/** + * Unit tests for {@link EvaluateModel}. + */ +public class EvaluateModelTests extends TestCase { + + public void testMerge() { + EvaluateModel child = new EvaluateModel("child"); + EvaluateModel parent = new EvaluateModel("parent"); + child.merge(parent); + assertEquals("child", child.getExpression()); + } + + public void testMergeNullParent() { + EvaluateModel child = new EvaluateModel("child"); + EvaluateModel parent = null; + child.merge(parent); + assertEquals("child", child.getExpression()); + } + + public void testMergeOverrideMatch() { + EvaluateModel child = new EvaluateModel("child"); + EvaluateModel parent = new EvaluateModel("child", "end"); + child.merge(parent); + assertEquals("end", child.getResult()); + } + + public void testMergeOverrideMatchFailed() { + EvaluateModel child = new EvaluateModel("child"); + EvaluateModel parent = new EvaluateModel("parent", "end"); + child.merge(parent); + assertEquals(null, child.getResult()); + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/ExceptionHandlerModelTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/ExceptionHandlerModelTests.java new file mode 100644 index 00000000..0084f885 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/ExceptionHandlerModelTests.java @@ -0,0 +1,49 @@ +/* + * 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.model; + +import org.springframework.webflow.engine.model.ExceptionHandlerModel; + +import junit.framework.TestCase; + +/** + * Unit tests for {@link ExceptionHandlerModel}. + */ +public class ExceptionHandlerModelTests extends TestCase { + + public void testMerge() { + ExceptionHandlerModel child = new ExceptionHandlerModel("child"); + ExceptionHandlerModel parent = new ExceptionHandlerModel("parent"); + child.merge(parent); + assertEquals("child", child.getBeanName()); + } + + public void testMergeNullParent() { + ExceptionHandlerModel child = new ExceptionHandlerModel("child"); + ExceptionHandlerModel parent = null; + child.merge(parent); + assertEquals("child", child.getBeanName()); + } + + public void testMergeOverrideMatch() { + // exception handler will never merge + } + + public void testMergeOverrideMatchFailed() { + // exception handler will never merge + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/FlowModelTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/FlowModelTests.java new file mode 100644 index 00000000..c60027c4 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/FlowModelTests.java @@ -0,0 +1,188 @@ +/* + * 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.model; + +import junit.framework.TestCase; + +/** + * Unit tests for {@link FlowModel}. + */ +public class FlowModelTests extends TestCase { + + public void testMerge() { + FlowModel child = new FlowModel(); + child.setStartStateId("child"); + FlowModel parent = new FlowModel(); + parent.setStartStateId("parent"); + child.merge(parent); + assertEquals("child", child.getStartStateId()); + } + + public void testMergeNullParent() { + FlowModel child = new FlowModel(); + child.setStartStateId("child"); + FlowModel parent = null; + child.merge(parent); + assertEquals("child", child.getStartStateId()); + } + + public void testMergeOverrideMatch() { + FlowModel child = new FlowModel(); + FlowModel parent = new FlowModel(); + parent.addViewState(new ViewStateModel("view")); + child.merge(parent); + assertEquals(1, child.getStates().size()); + } + + public void testMergeOverrideMatchFailed() { + FlowModel child = new FlowModel(); + FlowModel parent = new FlowModel(); + parent.addViewState(new ViewStateModel("view")); + child.merge(parent); + // flows will always merge, regardless of likeness + assertEquals(1, child.getStates().size()); + } + + public void testIntegrationAttributes() { + FlowModel child = new FlowModel(); + FlowModel parent = new FlowModel(); + child.addAttribute(new AttributeModel("name", "value")); + parent.addAttribute(new AttributeModel("name", "value", "type")); + parent.addAttribute(new AttributeModel("name2", "value2", "type2")); + child.merge(parent); + assertEquals(2, child.getAttributes().size()); + assertEquals("name", ((AttributeModel) child.getAttributes().get(0)).getName()); + assertEquals("type", ((AttributeModel) child.getAttributes().get(0)).getType()); + assertEquals("name2", ((AttributeModel) child.getAttributes().get(1)).getName()); + assertEquals("type2", ((AttributeModel) child.getAttributes().get(1)).getType()); + } + + public void testIntegrationSecured() { + FlowModel child = new FlowModel(); + FlowModel parent = new FlowModel(); + child.setSecured(new SecuredModel("secured")); + parent.setSecured(new SecuredModel("secured", "all")); + child.merge(parent); + assertEquals("all", child.getSecured().getMatch()); + } + + public void testIntegrationPersistenceContext() { + FlowModel child = new FlowModel(); + FlowModel parent = new FlowModel(); + parent.setPersistenceContext(new PersistenceContextModel()); + child.merge(parent); + assertNotNull(child.getPersistenceContext()); + } + + public void testIntegrationVars() { + FlowModel child = new FlowModel(); + FlowModel parent = new FlowModel(); + child.addVar(new VarModel("name", "value")); + parent.addVar(new VarModel("name", "", "scope")); + parent.addVar(new VarModel("name2", "value2")); + child.merge(parent); + assertEquals(2, child.getVars().size()); + assertEquals("scope", ((VarModel) child.getVars().get(1)).getScope()); + } + + public void testIntegrationMappings() { + FlowModel child = new FlowModel(); + FlowModel parent = new FlowModel(); + child.addInput(new InputModel("name", "value")); + child.addInput(new InputModel("name2", "value2", "type2", "required2")); + child.addInput(new InputModel("name3", "value3", "type3", "required3")); + parent.addInput(new InputModel("name", "value", "type", "required")); + parent.addInput(new InputModel("name3", "value3", "type3", "required3")); + child.merge(parent); + assertEquals(3, child.getInputs().size()); + } + + public void testIntegrationOnStart() { + FlowModel child = new FlowModel(); + FlowModel parent = new FlowModel(); + child.addOnStartAction(new EvaluateModel("expression")); + child.addOnStartAction(new RenderModel("expression")); + child.addOnStartAction(new SetModel("expression", "value")); + parent.addOnStartAction(new EvaluateModel("expression", "result")); + parent.addOnStartAction(new RenderModel("expression")); + parent.addOnStartAction(new SetModel("expression", "value")); + child.merge(parent); + assertEquals(3, child.getOnStartActions().size()); + assertEquals("result", ((EvaluateModel) child.getOnStartActions().get(0)).getResult()); + } + + public void testIntegrationStates() { + FlowModel child = new FlowModel(); + FlowModel parent = new FlowModel(); + child.addViewState(new ViewStateModel("view")); + child.addEndState(new EndStateModel("end")); + parent.addViewState(new ViewStateModel("view", "jsp")); + parent.addState(new DecisionStateModel("decider")); + parent.addActionState(new ActionStateModel("end")); + child.merge(parent); + assertEquals(4, child.getStates().size()); + assertEquals("jsp", ((ViewStateModel) child.getStates().get(0)).getView()); + } + + public void testIntegrationGlobalTransitions() { + FlowModel child = new FlowModel(); + FlowModel parent = new FlowModel(); + child.addGlobalTransition(new TransitionModel("end")); + child.addGlobalTransition(new TransitionModel("start")); + parent.addGlobalTransition(new TransitionModel("search")); + parent.addGlobalTransition(new TransitionModel("end", "theend")); + child.merge(parent); + assertEquals(3, child.getGlobalTransitions().size()); + assertEquals("theend", ((TransitionModel) child.getGlobalTransitions().get(0)).getTo()); + } + + public void testIntegrationOnEnd() { + FlowModel child = new FlowModel(); + FlowModel parent = new FlowModel(); + child.addOnEndAction(new EvaluateModel("expression")); + child.addOnEndAction(new RenderModel("expression")); + child.addOnEndAction(new SetModel("expression", "value")); + parent.addOnEndAction(new EvaluateModel("expression", "result")); + parent.addOnEndAction(new RenderModel("expression")); + parent.addOnEndAction(new SetModel("expression", "value")); + child.merge(parent); + assertEquals(3, child.getOnEndActions().size()); + assertEquals("result", ((EvaluateModel) child.getOnEndActions().get(0)).getResult()); + } + + public void testIntegrationExceptionHandlers() { + FlowModel child = new FlowModel(); + FlowModel parent = new FlowModel(); + child.addExceptionHandler(new ExceptionHandlerModel("bean1")); + child.addExceptionHandler(new ExceptionHandlerModel("bean2")); + parent.addExceptionHandler(new ExceptionHandlerModel("bean2")); + parent.addExceptionHandler(new ExceptionHandlerModel("bean3")); + child.merge(parent); + assertEquals(3, child.getExceptionHandlers().size()); + } + + public void testIntegrationBeanImports() { + FlowModel child = new FlowModel(); + FlowModel parent = new FlowModel(); + child.addBeanImport(new BeanImportModel("path1")); + child.addBeanImport(new BeanImportModel("path2")); + parent.addBeanImport(new BeanImportModel("path2")); + parent.addBeanImport(new BeanImportModel("path3")); + child.merge(parent); + assertEquals(3, child.getBeanImports().size()); + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/IfModelTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/IfModelTests.java new file mode 100644 index 00000000..ed927753 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/IfModelTests.java @@ -0,0 +1,55 @@ +/* + * 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.model; + +import org.springframework.webflow.engine.model.IfModel; + +import junit.framework.TestCase; + +/** + * Unit tests for {@link IfModel}. + */ +public class IfModelTests extends TestCase { + + public void testMerge() { + IfModel child = new IfModel("child", "childthen"); + IfModel parent = new IfModel("parent", "parentthen"); + child.merge(parent); + assertEquals("childthen", child.getThen()); + } + + public void testMergeNullParent() { + IfModel child = new IfModel("child", "childthen"); + IfModel parent = null; + child.merge(parent); + assertEquals("childthen", child.getThen()); + } + + public void testMergeOverrideMatch() { + IfModel child = new IfModel("child", "childthen"); + IfModel parent = new IfModel("child", "childthen", "childelse"); + child.merge(parent); + assertEquals("childelse", child.getElse()); + } + + public void testMergeOverrideMatchFailed() { + IfModel child = new IfModel("child", "childthen"); + IfModel parent = new IfModel("parent", "parentthen", "parentelse"); + child.merge(parent); + assertEquals(null, child.getElse()); + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/InputModelTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/InputModelTests.java new file mode 100644 index 00000000..7634ad36 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/InputModelTests.java @@ -0,0 +1,57 @@ +/* + * 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.model; + +import org.springframework.webflow.engine.model.InputModel; + +import junit.framework.TestCase; + +/** + * Unit tests for {@link InputModel}. + */ +public class InputModelTests extends TestCase { + + public void testMerge() { + InputModel child = new InputModel("child", "childvalue"); + InputModel parent = new InputModel("parent", "parentvalue"); + child.merge(parent); + assertEquals("childvalue", child.getValue()); + } + + public void testMergeNullParent() { + InputModel child = new InputModel("child", "childvalue"); + InputModel parent = null; + child.merge(parent); + assertEquals("childvalue", child.getValue()); + } + + public void testMergeOverrideMatch() { + InputModel child = new InputModel("child", "childvalue"); + InputModel parent = new InputModel("child", "childvalue"); + parent.setType("long"); + child.merge(parent); + assertEquals("long", child.getType()); + } + + public void testMergeOverrideMatchFailed() { + InputModel child = new InputModel("child", "childvalue"); + InputModel parent = new InputModel("parent", "parentvalue"); + parent.setType("long"); + child.merge(parent); + assertEquals(null, child.getType()); + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/OutputModelTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/OutputModelTests.java new file mode 100644 index 00000000..58a84481 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/OutputModelTests.java @@ -0,0 +1,57 @@ +/* + * 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.model; + +import org.springframework.webflow.engine.model.OutputModel; + +import junit.framework.TestCase; + +/** + * Unit tests for {@link OutputModel}. + */ +public class OutputModelTests extends TestCase { + + public void testMerge() { + OutputModel child = new OutputModel("child", "childvalue"); + OutputModel parent = new OutputModel("parent", "parentvalue"); + child.merge(parent); + assertEquals("childvalue", child.getValue()); + } + + public void testMergeNullParent() { + OutputModel child = new OutputModel("child", "childvalue"); + OutputModel parent = null; + child.merge(parent); + assertEquals("childvalue", child.getValue()); + } + + public void testMergeOverrideMatch() { + OutputModel child = new OutputModel("child", "childvalue"); + OutputModel parent = new OutputModel("child", "childvalue"); + parent.setType("long"); + child.merge(parent); + assertEquals("long", child.getType()); + } + + public void testMergeOverrideMatchFailed() { + OutputModel child = new OutputModel("child", "childvalue"); + OutputModel parent = new OutputModel("parent", "parentvalue"); + parent.setType("long"); + child.merge(parent); + assertEquals(null, child.getType()); + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/PersistenceContextModelTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/PersistenceContextModelTests.java new file mode 100644 index 00000000..72126efe --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/PersistenceContextModelTests.java @@ -0,0 +1,31 @@ +/* + * 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.model; + +import org.springframework.webflow.engine.model.PersistenceContextModel; + +import junit.framework.TestCase; + +/** + * Unit tests for {@link PersistenceContextModel}. + */ +public class PersistenceContextModelTests extends TestCase { + + public void test() { + // no op + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/RenderModelTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/RenderModelTests.java new file mode 100644 index 00000000..bd8cb585 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/RenderModelTests.java @@ -0,0 +1,49 @@ +/* + * 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.model; + +import org.springframework.webflow.engine.model.RenderModel; + +import junit.framework.TestCase; + +/** + * Unit tests for {@link RenderModel}. + */ +public class RenderModelTests extends TestCase { + + public void testMerge() { + RenderModel child = new RenderModel("child"); + RenderModel parent = new RenderModel("parent"); + child.merge(parent); + assertEquals("child", child.getFragments()); + } + + public void testMergeNullParent() { + RenderModel child = new RenderModel("child"); + RenderModel parent = null; + child.merge(parent); + assertEquals("child", child.getFragments()); + } + + public void testMergeOverrideMatch() { + // render will never merge + } + + public void testMergeOverrideMatchFailed() { + // render will never merge + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/SecuredModelTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/SecuredModelTests.java new file mode 100644 index 00000000..89b28150 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/SecuredModelTests.java @@ -0,0 +1,55 @@ +/* + * 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.model; + +import org.springframework.webflow.engine.model.SecuredModel; + +import junit.framework.TestCase; + +/** + * Unit tests for {@link SecuredModel}. + */ +public class SecuredModelTests extends TestCase { + + public void testMerge() { + SecuredModel child = new SecuredModel("child"); + SecuredModel parent = new SecuredModel("parent"); + child.merge(parent); + assertEquals("child", child.getAttributes()); + } + + public void testMergeNullParent() { + SecuredModel child = new SecuredModel("child"); + SecuredModel parent = null; + child.merge(parent); + assertEquals("child", child.getAttributes()); + } + + public void testMergeOverrideMatch() { + SecuredModel child = new SecuredModel("child"); + SecuredModel parent = new SecuredModel("child", "all"); + child.merge(parent); + assertEquals("all", child.getMatch()); + } + + public void testMergeOverrideMatchFailed() { + SecuredModel child = new SecuredModel("child"); + SecuredModel parent = new SecuredModel("parent", "all"); + child.merge(parent); + assertEquals(null, child.getMatch()); + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/SetModelTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/SetModelTests.java new file mode 100644 index 00000000..ce8be1e9 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/SetModelTests.java @@ -0,0 +1,55 @@ +/* + * 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.model; + +import org.springframework.webflow.engine.model.SetModel; + +import junit.framework.TestCase; + +/** + * Unit tests for {@link SetModel}. + */ +public class SetModelTests extends TestCase { + + public void testMerge() { + SetModel child = new SetModel("child", "childvalue"); + SetModel parent = new SetModel("parent", "parentvalue"); + child.merge(parent); + assertEquals("child", child.getName()); + } + + public void testMergeNullParent() { + SetModel child = new SetModel("child", "childvalue"); + SetModel parent = null; + child.merge(parent); + assertEquals("child", child.getName()); + } + + public void testMergeOverrideMatch() { + SetModel child = new SetModel("child", "childvalue"); + SetModel parent = new SetModel("child", "childvalue", "childtype"); + child.merge(parent); + assertEquals("childtype", child.getType()); + } + + public void testMergeOverrideMatchFailed() { + SetModel child = new SetModel("child", "childvalue"); + SetModel parent = new SetModel("parent", "parentvalue", "parenttype"); + child.merge(parent); + assertEquals(null, child.getType()); + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/SubflowStateModelTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/SubflowStateModelTests.java new file mode 100644 index 00000000..004d4ed2 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/SubflowStateModelTests.java @@ -0,0 +1,55 @@ +/* + * 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.model; + +import junit.framework.TestCase; + +/** + * Unit tests for {@link SubflowStateModel}. + */ +public class SubflowStateModelTests extends TestCase { + + public void testMerge() { + SubflowStateModel child = new SubflowStateModel("child", "childflow"); + SubflowStateModel parent = new SubflowStateModel("parent", "parentflow"); + child.merge(parent); + assertEquals("child", child.getId()); + } + + public void testMergeNullParent() { + SubflowStateModel child = new SubflowStateModel("child", "childflow"); + SubflowStateModel parent = null; + child.merge(parent); + assertEquals("child", child.getId()); + } + + public void testMergeOverrideMatch() { + SubflowStateModel child = new SubflowStateModel("child", "childflow"); + SubflowStateModel parent = new SubflowStateModel("child", "parentflow"); + parent.addInput(new InputModel("inname", "invalue")); + child.merge(parent); + assertEquals(1, child.getInputs().size()); + } + + public void testMergeOverrideMatchFailed() { + SubflowStateModel child = new SubflowStateModel("child", "childflow"); + SubflowStateModel parent = new SubflowStateModel("parent", "parentflow"); + parent.addInput(new InputModel("inname", "invalue")); + child.merge(parent); + assertEquals(null, child.getInputs()); + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/TransitionModelTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/TransitionModelTests.java new file mode 100644 index 00000000..0363b83c --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/TransitionModelTests.java @@ -0,0 +1,55 @@ +/* + * 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.model; + +import org.springframework.webflow.engine.model.TransitionModel; + +import junit.framework.TestCase; + +/** + * Unit tests for {@link TransitionModel}. + */ +public class TransitionModelTests extends TestCase { + + public void testMerge() { + TransitionModel child = new TransitionModel("child"); + TransitionModel parent = new TransitionModel("parent"); + child.merge(parent); + assertEquals("child", child.getOn()); + } + + public void testMergeNullParent() { + TransitionModel child = new TransitionModel("child"); + TransitionModel parent = null; + child.merge(parent); + assertEquals("child", child.getOn()); + } + + public void testMergeOverrideMatch() { + TransitionModel child = new TransitionModel("child"); + TransitionModel parent = new TransitionModel("child", "end"); + child.merge(parent); + assertEquals("end", child.getTo()); + } + + public void testMergeOverrideMatchFailed() { + TransitionModel child = new TransitionModel("child"); + TransitionModel parent = new TransitionModel("parent", "end"); + child.merge(parent); + assertEquals(null, child.getTo()); + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/VarModelTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/VarModelTests.java new file mode 100644 index 00000000..63563d8c --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/VarModelTests.java @@ -0,0 +1,55 @@ +/* + * 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.model; + +import org.springframework.webflow.engine.model.VarModel; + +import junit.framework.TestCase; + +/** + * Unit tests for {@link VarModel}. + */ +public class VarModelTests extends TestCase { + + public void testMerge() { + VarModel child = new VarModel("child", "childclass"); + VarModel parent = new VarModel("parent", "parentclass"); + child.merge(parent); + assertEquals("child", child.getName()); + } + + public void testMergeNullParent() { + VarModel child = new VarModel("child", "childclass"); + VarModel parent = null; + child.merge(parent); + assertEquals("child", child.getName()); + } + + public void testMergeOverrideMatch() { + VarModel child = new VarModel("child", "childclass"); + VarModel parent = new VarModel("child", "childclass", "childscope"); + child.merge(parent); + assertEquals("childscope", child.getScope()); + } + + public void testMergeOverrideMatchFailed() { + VarModel child = new VarModel("child", "childclass"); + VarModel parent = new VarModel("parent", "parentclass", "parentscope"); + child.merge(parent); + assertEquals(null, child.getScope()); + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/ViewStateModelTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/ViewStateModelTests.java new file mode 100644 index 00000000..0b70b642 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/ViewStateModelTests.java @@ -0,0 +1,55 @@ +/* + * 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.model; + +import org.springframework.webflow.engine.model.ViewStateModel; + +import junit.framework.TestCase; + +/** + * Unit tests for {@link ViewStateModel}. + */ +public class ViewStateModelTests extends TestCase { + + public void testMerge() { + ViewStateModel child = new ViewStateModel("child", "childview"); + ViewStateModel parent = new ViewStateModel("parent", "parentview"); + child.merge(parent); + assertEquals("childview", child.getView()); + } + + public void testMergeNullParent() { + ViewStateModel child = new ViewStateModel("child", "childview"); + ViewStateModel parent = null; + child.merge(parent); + assertEquals("childview", child.getView()); + } + + public void testMergeOverrideMatch() { + ViewStateModel child = new ViewStateModel("child"); + ViewStateModel parent = new ViewStateModel("child", "parentview"); + child.merge(parent); + assertEquals("parentview", child.getView()); + } + + public void testMergeOverrideMatchFailed() { + ViewStateModel child = new ViewStateModel("child"); + ViewStateModel parent = new ViewStateModel("parent", "parentview"); + child.merge(parent); + assertEquals(null, child.getView()); + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/XmlFlowModelBuilderTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/XmlFlowModelBuilderTests.java new file mode 100644 index 00000000..4a60e06a --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/XmlFlowModelBuilderTests.java @@ -0,0 +1,275 @@ +package org.springframework.webflow.engine.model.builder.xml; + +import junit.framework.TestCase; + +import org.springframework.beans.factory.support.StaticListableBeanFactory; +import org.springframework.core.io.ClassPathResource; +import org.springframework.webflow.engine.model.AbstractStateModel; +import org.springframework.webflow.engine.model.AttributeModel; +import org.springframework.webflow.engine.model.FlowModel; +import org.springframework.webflow.engine.model.SecuredModel; +import org.springframework.webflow.engine.model.TransitionModel; +import org.springframework.webflow.engine.model.VarModel; +import org.springframework.webflow.engine.model.ViewStateModel; +import org.springframework.webflow.engine.model.builder.FlowModelBuilder; +import org.springframework.webflow.engine.model.registry.DefaultFlowModelHolder; +import org.springframework.webflow.engine.model.registry.FlowModelConstructionException; +import org.springframework.webflow.engine.model.registry.FlowModelRegistry; +import org.springframework.webflow.engine.model.registry.FlowModelRegistryImpl; + +public class XmlFlowModelBuilderTests extends TestCase { + + private FlowModelRegistry registry; + + protected void setUp() { + StaticListableBeanFactory beanFactory = new StaticListableBeanFactory(); + beanFactory.addBean("bean", new Object()); + registry = new FlowModelRegistryImpl(); + } + + // public void testBuildIncompleteFlow() { + // ClassPathResource resource = new ClassPathResource("flow-incomplete.xml", getClass()); + // builder = new XmlModelBuilder(resource); + // try { + // builder.parse(); + // fail("Should have failed"); + // } catch (FlowBuilderException e) { + // } + // } + + public void testBuildFlowWithEndState() { + ClassPathResource resource = new ClassPathResource("flow-endstate.xml", getClass()); + FlowModelBuilder builder = new XmlFlowModelBuilder(resource, registry); + builder.init(); + builder.build(); + FlowModel flow = builder.getFlowModel(); + assertNull(flow.getStartStateId()); + assertEquals("end", ((AbstractStateModel) flow.getStates().get(0)).getId()); + } + + public void testBuildFlowWithDefaultStartState() { + ClassPathResource resource = new ClassPathResource("flow-startstate-default.xml", getClass()); + FlowModelBuilder builder = new XmlFlowModelBuilder(resource, registry); + builder.init(); + builder.build(); + FlowModel flow = builder.getFlowModel(); + assertNull(flow.getStartStateId()); + assertEquals("end", ((AbstractStateModel) flow.getStates().get(0)).getId()); + } + + public void testBuildFlowWithStartStateAttribute() { + ClassPathResource resource = new ClassPathResource("flow-startstate-attribute.xml", getClass()); + FlowModelBuilder builder = new XmlFlowModelBuilder(resource, registry); + builder.init(); + builder.build(); + FlowModel flow = builder.getFlowModel(); + assertEquals("end", flow.getStartStateId()); + } + + public void testCustomFlowAttribute() { + ClassPathResource resource = new ClassPathResource("flow-custom-attribute.xml", getClass()); + FlowModelBuilder builder = new XmlFlowModelBuilder(resource, registry); + builder.init(); + builder.build(); + FlowModel flow = builder.getFlowModel(); + assertEquals("bar", ((AttributeModel) flow.getAttributes().get(0)).getValue()); + assertEquals("number", ((AttributeModel) flow.getAttributes().get(1)).getName()); + } + + public void testPersistenceContextFlow() { + ClassPathResource resource = new ClassPathResource("flow-persistencecontext.xml", getClass()); + FlowModelBuilder builder = new XmlFlowModelBuilder(resource, registry); + builder.init(); + builder.build(); + FlowModel flow = builder.getFlowModel(); + assertNotNull(flow.getPersistenceContext()); + } + + // public void testFlowInputOutputMapping() { + // ClassPathResource resource = new ClassPathResource("flow-inputoutput.xml", getClass()); + // FlowModelBuilder builder = new XmlFlowModelBuilder(resource, registry); + // builder.init(); + // builder.build(); + // FlowModel flow = builder.getFlowModel(); + // FlowExecutionImplFactory factory = new FlowExecutionImplFactory(); + // FlowExecution execution = factory.createFlowExecution(flow); + // MockExternalContext context = new MockExternalContext(); + // MutableAttributeMap input = new LocalAttributeMap(); + // input.put("foo", "bar"); + // input.put("number", "3"); + // input.put("required", "9"); + // execution.start(input, context); + // Event outcome = execution.getOutcome(); + // assertEquals("end", outcome.getId()); + // assertEquals("bar", outcome.getAttributes().get("foo")); + // assertEquals("bar", outcome.getAttributes().get("differentName")); + // assertEquals(new Integer(3), outcome.getAttributes().get("number")); + // assertEquals(new Integer(3), outcome.getAttributes().get("required")); + // assertEquals("a literal", outcome.getAttributes().get("literal")); + // assertNull(outcome.getAttributes().get("notReached")); + // } + + // public void testFlowRequiredInputMapping() { + // ClassPathResource resource = new ClassPathResource("flow-inputoutput.xml", getClass()); + // FlowModelBuilder builder = new XmlFlowModelBuilder(resource, registry); + // builder.init(); + // builder.build(); + // FlowModel flow = builder.getFlowModel(); + // FlowExecutionImplFactory factory = new FlowExecutionImplFactory(); + // FlowExecution execution = factory.createFlowExecution(flow); + // MockExternalContext context = new MockExternalContext(); + // MutableAttributeMap input = new LocalAttributeMap(); + // try { + // execution.start(input, context); + // fail("Should have failed"); + // } catch (FlowExecutionException e) { + // RequiredMappingException me = (RequiredMappingException) e.getRootCause(); + // } + // } + + // public void testFlowRequiredOutputMapping() { + // ClassPathResource resource = new ClassPathResource("flow-inputoutput.xml", getClass()); + // FlowModelBuilder builder = new XmlFlowModelBuilder(resource, registry); + // builder.init(); + // builder.build(); + // FlowModel flow = builder.getFlowModel(); + // FlowExecutionImplFactory factory = new FlowExecutionImplFactory(); + // FlowExecution execution = factory.createFlowExecution(flow); + // MockExternalContext context = new MockExternalContext(); + // MutableAttributeMap input = new LocalAttributeMap(); + // input.put("required", "yo"); + // try { + // execution.start(input, context); + // fail("Should have failed"); + // } catch (FlowExecutionException e) { + // RequiredMappingException me = (RequiredMappingException) e.getRootCause(); + // } + // } + + public void testFlowSecured() { + ClassPathResource resource = new ClassPathResource("flow-secured.xml", getClass()); + FlowModelBuilder builder = new XmlFlowModelBuilder(resource, registry); + builder.init(); + builder.build(); + FlowModel flow = builder.getFlowModel(); + SecuredModel secured = flow.getSecured(); + assertNotNull(secured); + assertEquals("ROLE_USER", secured.getAttributes()); + } + + public void testFlowSecuredState() { + ClassPathResource resource = new ClassPathResource("flow-secured-state.xml", getClass()); + FlowModelBuilder builder = new XmlFlowModelBuilder(resource, registry); + builder.init(); + builder.build(); + FlowModel flow = builder.getFlowModel(); + SecuredModel secured = ((AbstractStateModel) flow.getStates().get(0)).getSecured(); + assertNotNull(secured); + assertEquals("ROLE_USER", secured.getAttributes()); + } + + public void testFlowSecuredTransition() { + ClassPathResource resource = new ClassPathResource("flow-secured-transition.xml", getClass()); + FlowModelBuilder builder = new XmlFlowModelBuilder(resource, registry); + builder.init(); + builder.build(); + FlowModel flow = builder.getFlowModel(); + SecuredModel secured = ((TransitionModel) flow.getGlobalTransitions().get(0)).getSecured(); + assertNotNull(secured); + assertEquals("ROLE_USER", secured.getAttributes()); + } + + public void testFlowVariable() { + ClassPathResource resource = new ClassPathResource("flow-var.xml", getClass()); + FlowModelBuilder builder = new XmlFlowModelBuilder(resource, registry); + builder.init(); + builder.build(); + FlowModel flow = builder.getFlowModel(); + assertEquals("flow-foo", ((VarModel) flow.getVars().get(0)).getName()); + assertEquals(null, ((VarModel) flow.getVars().get(0)).getScope()); + assertEquals("conversation-foo", ((VarModel) flow.getVars().get(1)).getName()); + assertEquals("conversation", ((VarModel) flow.getVars().get(1)).getScope()); + } + + public void testViewStateVariable() { + ClassPathResource resource = new ClassPathResource("flow-viewstate-var.xml", getClass()); + FlowModelBuilder builder = new XmlFlowModelBuilder(resource, registry); + builder.init(); + builder.build(); + FlowModel flow = builder.getFlowModel(); + assertEquals("foo", ((VarModel) ((ViewStateModel) flow.getStates().get(0)).getVars().get(0)).getName()); + } + + public void testViewStateRedirect() { + ClassPathResource resource = new ClassPathResource("flow-viewstate-redirect.xml", getClass()); + FlowModelBuilder builder = new XmlFlowModelBuilder(resource, registry); + builder.init(); + builder.build(); + FlowModel flow = builder.getFlowModel(); + assertEquals("true", ((ViewStateModel) flow.getStates().get(0)).getRedirect()); + } + + public void testViewStatePopup() { + ClassPathResource resource = new ClassPathResource("flow-viewstate-popup.xml", getClass()); + FlowModelBuilder builder = new XmlFlowModelBuilder(resource, registry); + builder.init(); + builder.build(); + FlowModel flow = builder.getFlowModel(); + assertEquals("true", ((ViewStateModel) flow.getStates().get(0)).getPopup()); + } + + // public void testViewStateFlowRedirect() { + // ClassPathResource resource = new ClassPathResource("flow-viewstate-flowredirect.xml", + // getClass()); + // FlowModelBuilder builder = new XmlFlowModelBuilder(resource, registry); + // builder.init(); + // builder.build(); + // FlowModel flow = builder.getFlowModel(); + // ViewFactory vf = ((ViewState) flow.getStateInstance("view")).getViewFactory(); + // assertTrue(vf instanceof ActionExecutingViewFactory); + // ActionExecutingViewFactory avf = (ActionExecutingViewFactory) vf; + // assertTrue(avf.getAction() instanceof FlowDefinitionRedirectAction); + // } + + // public void testViewStateExternalRedirect() { + // ClassPathResource resource = new ClassPathResource("flow-viewstate-externalredirect.xml", + // getClass()); + // FlowModelBuilder builder = new XmlFlowModelBuilder(resource, registry); + // builder.init(); + // builder.build(); + // FlowModel flow = builder.getFlowModel(); + // ViewFactory vf = ((ViewState) flow.getStateInstance("view")).getViewFactory(); + // assertTrue(vf instanceof ActionExecutingViewFactory); + // ActionExecutingViewFactory avf = (ActionExecutingViewFactory) vf; + // assertTrue(avf.getAction() instanceof ExternalRedirectAction); + // } + + public void testMerge() { + ClassPathResource resourceChild = new ClassPathResource("flow-inheritance-child.xml", getClass()); + ClassPathResource resourceParent = new ClassPathResource("flow-inheritance-parent.xml", getClass()); + registry + .registerFlowModel(new DefaultFlowModelHolder(new XmlFlowModelBuilder(resourceChild, registry), "child")); + registry.registerFlowModel(new DefaultFlowModelHolder(new XmlFlowModelBuilder(resourceParent, registry), + "parent")); + FlowModel flow = registry.getFlowModel("child"); + assertEquals(1, flow.getGlobalTransitions().size()); + assertEquals(2, flow.getStates().size()); + assertEquals("view", ((AbstractStateModel) flow.getStates().get(0)).getId()); + } + + public void testMergeParentNotFound() { + ClassPathResource resourceChild = new ClassPathResource("flow-inheritance-child.xml", getClass()); + ClassPathResource resourceParent = new ClassPathResource("flow-inheritance-parent.xml", getClass()); + registry + .registerFlowModel(new DefaultFlowModelHolder(new XmlFlowModelBuilder(resourceChild, registry), "child")); + registry.registerFlowModel(new DefaultFlowModelHolder(new XmlFlowModelBuilder(resourceParent, registry), + "parent-id-not-matching")); + try { + registry.getFlowModel("child"); + fail("A FlowModelConstructionException was expected"); + } catch (FlowModelConstructionException e) { + // we want this + } + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-action-evaluate-action.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-action-evaluate-action.xml similarity index 100% rename from spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-action-evaluate-action.xml rename to spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-action-evaluate-action.xml diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-action-evaluate-bean.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-action-evaluate-bean.xml similarity index 100% rename from spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-action-evaluate-bean.xml rename to spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-action-evaluate-bean.xml diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-custom-attribute.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-custom-attribute.xml similarity index 100% rename from spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-custom-attribute.xml rename to spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-custom-attribute.xml diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-endstate.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-endstate.xml similarity index 100% rename from spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-endstate.xml rename to spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-endstate.xml diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-incomplete.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-incomplete.xml similarity index 100% rename from spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-incomplete.xml rename to spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-incomplete.xml diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-inheritance-child.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-inheritance-child.xml new file mode 100644 index 00000000..d76941ab --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-inheritance-child.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-secured-transition.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-inheritance-parent.xml similarity index 100% rename from spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-secured-transition.xml rename to spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-inheritance-parent.xml diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-inputoutput.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-inputoutput.xml similarity index 100% rename from spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-inputoutput.xml rename to spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-inputoutput.xml diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-persistencecontext.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-persistencecontext.xml similarity index 100% rename from spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-persistencecontext.xml rename to spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-persistencecontext.xml diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-secured-state.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-secured-state.xml similarity index 100% rename from spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-secured-state.xml rename to spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-secured-state.xml diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-secured-transition.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-secured-transition.xml new file mode 100644 index 00000000..d006bd3c --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-secured-transition.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-secured.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-secured.xml similarity index 100% rename from spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-secured.xml rename to spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-secured.xml diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-startstate-attribute.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-startstate-attribute.xml similarity index 100% rename from spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-startstate-attribute.xml rename to spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-startstate-attribute.xml diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-startstate-default.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-startstate-default.xml similarity index 100% rename from spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-startstate-default.xml rename to spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-startstate-default.xml diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-var.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-var.xml similarity index 100% rename from spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-var.xml rename to spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-var.xml diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-externalredirect.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-viewstate-externalredirect.xml similarity index 100% rename from spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-externalredirect.xml rename to spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-viewstate-externalredirect.xml diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-flowredirect.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-viewstate-flowredirect.xml similarity index 100% rename from spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-flowredirect.xml rename to spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-viewstate-flowredirect.xml diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-popup.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-viewstate-popup.xml similarity index 100% rename from spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-popup.xml rename to spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-viewstate-popup.xml diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-redirect.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-viewstate-redirect.xml similarity index 100% rename from spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-redirect.xml rename to spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-viewstate-redirect.xml diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-var.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-viewstate-var.xml similarity index 100% rename from spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-var.xml rename to spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-viewstate-var.xml diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-view.xml b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-viewstate-view.xml similarity index 100% rename from spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/flow-viewstate-view.xml rename to spring-webflow/src/test/java/org/springframework/webflow/engine/model/builder/xml/flow-viewstate-view.xml diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/registry/DefaultFlowModelHolderTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/registry/DefaultFlowModelHolderTests.java new file mode 100644 index 00000000..5c83e94a --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/registry/DefaultFlowModelHolderTests.java @@ -0,0 +1,71 @@ +package org.springframework.webflow.engine.model.registry; + +import junit.framework.TestCase; + +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.webflow.engine.model.AbstractStateModel; +import org.springframework.webflow.engine.model.EndStateModel; +import org.springframework.webflow.engine.model.FlowModel; +import org.springframework.webflow.engine.model.builder.FlowModelBuilder; +import org.springframework.webflow.engine.model.builder.FlowModelBuilderException; +import org.springframework.webflow.util.ResourceHolder; + +public class DefaultFlowModelHolderTests extends TestCase { + private DefaultFlowModelHolder holder; + private FlowModelBuilder builder; + + protected void setUp() { + builder = new SimpleFlowBuilder(); + holder = new DefaultFlowModelHolder(builder, "flowId"); + } + + public void testGetFlowDefinition() { + FlowModel flow = holder.getFlowModel(); + assertNull(flow.getStartStateId()); + assertEquals("end", ((AbstractStateModel) flow.getStates().get(0)).getId()); + } + + public void testGetFlowDefinitionWithChangesRefreshed() { + FlowModel flow = holder.getFlowModel(); + holder.refresh(); + flow = holder.getFlowModel(); + assertNull(flow.getStartStateId()); + assertEquals("end", ((AbstractStateModel) flow.getStates().get(0)).getId()); + } + + public class SimpleFlowBuilder implements FlowModelBuilder { + + public FlowModel getFlowModel() throws FlowModelBuilderException { + FlowModel flow = new FlowModel(); + flow.addEndState(new EndStateModel("end")); + return flow; + } + + public void build() throws FlowModelBuilderException { + // no-op + } + + public void mergeParent() throws FlowModelBuilderException { + // no-op + } + + public void dispose() throws FlowModelBuilderException { + // no-op + } + + public void init() throws FlowModelBuilderException { + // no-op + } + + } + + public class ChangeDetectableFlowBuilder extends SimpleFlowBuilder implements ResourceHolder { + private FileSystemResource resource = new FileSystemResource("file.txt"); + + public Resource getResource() { + return resource; + } + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/registry/FlowModelRegistryImplTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/registry/FlowModelRegistryImplTests.java new file mode 100644 index 00000000..38971875 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/registry/FlowModelRegistryImplTests.java @@ -0,0 +1,58 @@ +package org.springframework.webflow.engine.model.registry; + +import junit.framework.TestCase; + +import org.springframework.webflow.engine.model.FlowModel; + +public class FlowModelRegistryImplTests extends TestCase { + + private FlowModelRegistryImpl registry = new FlowModelRegistryImpl(); + + private FlowModel fooFlow; + + private FlowModel barFlow; + + protected void setUp() { + fooFlow = new FlowModel(); + barFlow = new FlowModel(); + } + + public void testNoSuchFlowDefinition() { + try { + registry.getFlowModel("bogus"); + fail("Should've bombed with NoSuchFlow"); + } catch (NoSuchFlowModelException e) { + + } + } + + public void testRegisterFlow() { + registry.registerFlowModel(new DefaultFlowModelHolder(fooFlow, "foo")); + assertEquals(fooFlow, registry.getFlowModel("foo")); + } + + public void testRegisterFlowSameIds() { + registry.registerFlowModel(new DefaultFlowModelHolder(fooFlow, "foo")); + FlowModel newFlow = new FlowModel(); + registry.registerFlowModel(new DefaultFlowModelHolder(newFlow, "foo")); + assertSame(newFlow, registry.getFlowModel("foo")); + } + + public void testRegisterMultipleFlows() { + registry.registerFlowModel(new DefaultFlowModelHolder(fooFlow, "foo")); + registry.registerFlowModel(new DefaultFlowModelHolder(barFlow, "bar")); + assertEquals(fooFlow, registry.getFlowModel("foo")); + assertEquals(barFlow, registry.getFlowModel("bar")); + } + + public void testParentHierarchy() { + testRegisterMultipleFlows(); + FlowModelRegistryImpl child = new FlowModelRegistryImpl(); + child.setParent(registry); + FlowModel fooFlow = new FlowModel(); + child.registerFlowModel(new DefaultFlowModelHolder(fooFlow, "foo")); + assertSame(fooFlow, child.getFlowModel("foo")); + assertEquals(barFlow, child.getFlowModel("bar")); + } + +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/model/registry/file.txt b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/registry/file.txt new file mode 100644 index 00000000..47df90da --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/model/registry/file.txt @@ -0,0 +1 @@ +a changeable file \ No newline at end of file