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:
+ *
+ * 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: + *
+ * 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 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
+ * 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 @@
+
-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.
-]]>
-
+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.
+]]>
+
-
+A flow may also exhibit the following characteristics:
+
+
-]]>
-
-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.
-]]>
-
-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>
-
-]]>
-
-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.
-]]>
-
-Sophisticated transitional expressions are also supported when enclosed in a delimited expression:
-
- <transition on="#{event == 'submit' &;amp;& flowScope.attribute == 'foo'}" to="state"/>
-
-]]>
-
+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.
+]]>
+
- ${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.
-]]>
-
+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>
+
]]>
- <if test="#{criteria}" then="trueStateId" else="falseStateId"/>
-
-]]>
-
+ <if test="#{criteria}" then="trueStateId" else="falseStateId"/>
+
+]]>
+
+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.
]]>
- <bean-import resource="orderitem-flow-beans.xml"/>
-
-... would look for 'orderitem-flow-beans.xml' in the same directory as this document.
-]]>
-
+ <bean-import resource="orderitem-flow-beans.xml"/>
+
+... would look for 'orderitem-flow-beans.xml' in the same directory as this document.
+]]>
+
+Sophisticated transitional expressions are also supported when enclosed in a delimited expression:
+
+ <transition on="#{event == 'submit' &;amp;& flowScope.attribute == 'foo'}" to="state"/>
+
+]]>
+
+Note: Cannot be used in conjunction with a secured element.
+]]>
+ 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.
+ *