Updated the config schema and bean definition parser to accomodate changes from SWF-210.
This commit is contained in:
@@ -20,6 +20,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.core.enums.StaticLabeledEnumResolver;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.xml.DomUtils;
|
||||
import org.w3c.dom.Element;
|
||||
@@ -32,24 +33,41 @@ import org.w3c.dom.Element;
|
||||
class ExecutorBeanDefinitionParser extends AbstractBeanDefinitionParser {
|
||||
|
||||
// elements and attributes
|
||||
|
||||
|
||||
private static final String CONVERSATION_MANAGER_REF_ATTRIBUTE = "conversation-manager-ref";
|
||||
|
||||
private static final String EXECUTION_ATTRIBUTES_ELEMENT = "execution-attributes";
|
||||
|
||||
private static final String EXECUTION_LISTENERS_ELEMENT = "execution-listeners";
|
||||
|
||||
private static final String MAX_CONTINUATIONS_ATTRIBUTE = "max-continuations";
|
||||
|
||||
private static final String MAX_CONVERSATIONS_ATTRIBUTE = "max-conversations";
|
||||
|
||||
private static final String REGISTRY_REF_ATTRIBUTE = "registry-ref";
|
||||
|
||||
private static final String REPOSITORY_ELEMENT = "repository";
|
||||
|
||||
private static final String REPOSITORY_TYPE_ATTRIBUTE = "repository-type";
|
||||
|
||||
private static final String TYPE_ATTRIBUTE = "type";
|
||||
|
||||
// properties
|
||||
|
||||
private static final String CONVERSATION_MANAGER_PROPERTY = "conversationManager";
|
||||
|
||||
private static final String DEFINITION_LOCATOR_PROPERTY = "definitionLocator";
|
||||
|
||||
private static final String EXECUTION_ATTRIBUTES_PROPERTY = "executionAttributes";
|
||||
|
||||
private static final String EXECUTION_LISTENER_LOADER_PROPERTY = "executionListenerLoader";
|
||||
|
||||
private static final String MAX_CONTINUATIONS_PROPERTY = "maxContinuations";
|
||||
|
||||
private static final String MAX_CONVERSATIONS_PROPERTY = "maxConversations";
|
||||
|
||||
private static final String REPOSITORY_TYPE_PROPERTY = "repositoryType";
|
||||
|
||||
|
||||
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
|
||||
BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder
|
||||
@@ -57,10 +75,54 @@ class ExecutorBeanDefinitionParser extends AbstractBeanDefinitionParser {
|
||||
definitionBuilder.addPropertyReference(DEFINITION_LOCATOR_PROPERTY, getRegistryRef(element));
|
||||
addExecutionAttributes(element, parserContext, definitionBuilder);
|
||||
addExecutionListenerLoader(element, parserContext, definitionBuilder);
|
||||
definitionBuilder.addPropertyValue(REPOSITORY_TYPE_PROPERTY, getRepositoryType(element));
|
||||
configureRepository(element, definitionBuilder);
|
||||
return definitionBuilder.getBeanDefinition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a repository based on the <code>repositoryType</code> attribute
|
||||
* or a <code>repository</code> tag.
|
||||
* @param element the root element to extract repository configuration from
|
||||
*/
|
||||
private void configureRepository(Element element, BeanDefinitionBuilder definitionBuilder) {
|
||||
Element repositoryElement = DomUtils.getChildElementByTagName(element, REPOSITORY_ELEMENT);
|
||||
String repositoryTypeAttribute = getRepositoryType(element);
|
||||
if(repositoryElement != null) {
|
||||
if(StringUtils.hasText(repositoryTypeAttribute)) {
|
||||
throw new IllegalArgumentException("The 'repositoryType' attribute of the 'executor' element must not have a value if there is a 'repository' element");
|
||||
}
|
||||
definitionBuilder.addPropertyValue(REPOSITORY_TYPE_PROPERTY, getType(repositoryElement));
|
||||
configureContinuations(repositoryElement, definitionBuilder);
|
||||
configureConversationManager(repositoryElement, definitionBuilder);
|
||||
} else if (StringUtils.hasText(repositoryTypeAttribute)) {
|
||||
definitionBuilder.addPropertyValue(REPOSITORY_TYPE_PROPERTY, repositoryTypeAttribute);
|
||||
}
|
||||
}
|
||||
|
||||
private void configureConversationManager(Element repositoryElement, BeanDefinitionBuilder definitionBuilder) {
|
||||
String conversationManagerRef = getConversationManagerRef(repositoryElement);
|
||||
String maxConversations = getMaxConversations(repositoryElement);
|
||||
if(StringUtils.hasText(conversationManagerRef)) {
|
||||
if(StringUtils.hasText(maxConversations)) {
|
||||
throw new IllegalArgumentException("The 'maxConversations' attribute of the 'repository' element must not have a value if there is a value for the 'conversation-manager-ref' attribute");
|
||||
}
|
||||
definitionBuilder.addPropertyReference(CONVERSATION_MANAGER_PROPERTY, conversationManagerRef);
|
||||
} else if (StringUtils.hasText(maxConversations)) {
|
||||
definitionBuilder.addPropertyValue(MAX_CONVERSATIONS_PROPERTY, maxConversations);
|
||||
}
|
||||
}
|
||||
|
||||
private void configureContinuations(Element repositoryElement, BeanDefinitionBuilder definitionBuilder) {
|
||||
RepositoryType repositoryType = (RepositoryType) StaticLabeledEnumResolver.instance().getLabeledEnumByLabel(RepositoryType.class, getRepositoryType(repositoryElement));
|
||||
String maxContinuations = getMaxContinuations(repositoryElement);
|
||||
if(StringUtils.hasText(maxContinuations)) {
|
||||
if(repositoryType != RepositoryType.CONTINUATION) {
|
||||
throw new IllegalArgumentException("The 'maxContinuations' attribute of the 'repository' element must not have a value if the 'type' attribute is not 'continuation'");
|
||||
}
|
||||
definitionBuilder.addPropertyValue(MAX_CONTINUATIONS_PROPERTY, maxContinuations);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the registry detailed in the bean definition.
|
||||
* @param element the element to extract the registry name from
|
||||
@@ -83,6 +145,43 @@ class ExecutorBeanDefinitionParser extends AbstractBeanDefinitionParser {
|
||||
private String getRepositoryType(Element element) {
|
||||
return element.getAttribute(REPOSITORY_TYPE_ATTRIBUTE).toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the repository type enum field detailed in the bean
|
||||
* definition.
|
||||
* @param element the element to extract the repository type from
|
||||
* @return the type of the repository
|
||||
*/
|
||||
private String getType(Element element) {
|
||||
return element.getAttribute(TYPE_ATTRIBUTE).toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of continuations detailed in the bean definition.
|
||||
* @param element the element to extract the max continuations from
|
||||
* @return the max continuations
|
||||
*/
|
||||
private String getMaxContinuations(Element element) {
|
||||
return element.getAttribute(MAX_CONTINUATIONS_ATTRIBUTE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of conversations detailed in the bean definition.
|
||||
* @param element the element to extract the max conversations from
|
||||
* @return the max conversations
|
||||
*/
|
||||
private String getMaxConversations(Element element) {
|
||||
return element.getAttribute(MAX_CONVERSATIONS_ATTRIBUTE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the conversation manager detailed in the bean definition.
|
||||
* @param element the element to extract the conversation manager name from
|
||||
* @return the name of the conversation manager
|
||||
*/
|
||||
private String getConversationManagerRef(Element element) {
|
||||
return element.getAttribute(CONVERSATION_MANAGER_REF_ATTRIBUTE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse execution attribute definitions contained in given element.
|
||||
|
||||
@@ -93,7 +93,17 @@ an execution repository.
|
||||
<xsd:complexType>
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="beans:identifiedType">
|
||||
<xsd:sequence>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="repository" type="repositoryType" minOccurs="0">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
<![CDATA[
|
||||
Explicit repository configuration for this executor. This element is used if configuration needs
|
||||
to be more fine grained than the repositoryType attribute on executor.
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
<xsd:element name="execution-attributes" type="execution-attributesType" minOccurs="0">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
@@ -114,7 +124,7 @@ The listeners eligible for observing the lifecycle of executions launched by thi
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="repository-type" default="continuation">
|
||||
<xsd:attribute name="repository-type" type="repositoryTypeAttribute">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
<![CDATA[
|
||||
@@ -122,51 +132,7 @@ The type of execution repository to use. The repository is responsible for mana
|
||||
persistence between requests.
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:simpleType>
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="simple">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
<![CDATA[
|
||||
The simple repository type. Use of this strategy incurs minimal storage overhead but explicity prevents
|
||||
resubmits using the browser back button.
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:enumeration>
|
||||
<xsd:enumeration value="continuation">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
<![CDATA[
|
||||
The continuation repository type. Use this to snapshot flow execution state server-side to support use
|
||||
of the browser back button.
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:enumeration>
|
||||
<xsd:enumeration value="client">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
<![CDATA[
|
||||
The client continuation repository type. Use this to snapshot flow execution state client-side to support use of the browser
|
||||
back button in a stateless server environment.
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:enumeration>
|
||||
<xsd:enumeration value="singlekey">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
<![CDATA[
|
||||
The "single key" repository type. Use of this strategy assigns a single key per flow execution that remains
|
||||
constant throughout the life of the execution.
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:enumeration>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="registry-ref" type="xsd:string" use="required">
|
||||
<xsd:annotation>
|
||||
@@ -180,7 +146,95 @@ The idref to the registry this executor will use to locate flow definitions for
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:simpleType name="repositoryTypeAttribute">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="continuation">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
<![CDATA[
|
||||
The continuation repository type. Use this to snapshot flow execution state server-side to support use
|
||||
of the browser back button.
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:enumeration>
|
||||
<xsd:enumeration value="simple">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
<![CDATA[
|
||||
The simple repository type. Use of this strategy incurs minimal storage overhead but explicity prevents
|
||||
resubmits using the browser back button.
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:enumeration>
|
||||
<xsd:enumeration value="client">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
<![CDATA[
|
||||
The client continuation repository type. Use this to snapshot flow execution state client-side to support use of the browser
|
||||
back button in a stateless server environment.
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:enumeration>
|
||||
<xsd:enumeration value="singlekey">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
<![CDATA[
|
||||
The "single key" repository type. Use of this strategy assigns a single key per flow execution that remains
|
||||
constant throughout the life of the execution.
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:enumeration>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
<xsd:complexType name="repositoryType">
|
||||
<xsd:attribute name="type" type="repositoryTypeAttribute" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
<![CDATA[
|
||||
The type of execution repository to use. The repository is responsible for managing flow execution
|
||||
persistence between requests.
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="max-conversations" type="xsd:integer">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
<![CDATA[
|
||||
The maximum number of conversations to preserve in a repository. It is illegal to populate this
|
||||
attribute if you are also populating conversation-manager-ref.
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="max-continuations" type="xsd:integer">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
<![CDATA[
|
||||
The maximum number of continuations to preserve in a repository. This attribute is only useful
|
||||
only useful when the repository is of type 'continuation'.
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="conversation-manager-ref" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
<![CDATA[
|
||||
The idref to the conversation manager used by this registry to persist conversations. Populating
|
||||
this attribute implies that that you should not populate max-conversations.
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="execution-listenersType">
|
||||
<xsd:sequence>
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.webflow.config;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
@@ -66,6 +67,7 @@ public class WebFlowConfigNamespaceHandlerTests extends TestCase {
|
||||
|
||||
public void testDefaultExecutor() {
|
||||
FlowExecutorImpl flowExecutor = (FlowExecutorImpl)this.beanFactory.getBean("defaultExecutor");
|
||||
assertTrue(flowExecutor.getExecutionRepository() instanceof ContinuationFlowExecutionRepository);
|
||||
assertSame(this.beanFactory.getBean("withPathWithWildcards"), flowExecutor.getDefinitionLocator());
|
||||
AttributeMap attribs = ((FlowExecutionImplFactory)flowExecutor.getExecutionFactory()).getExecutionAttributes();
|
||||
assertEquals(1, attribs.size()); // defaults have been applied
|
||||
@@ -128,5 +130,32 @@ public class WebFlowConfigNamespaceHandlerTests extends TestCase {
|
||||
assertSame(StaticFlowExecutionListenerLoader.EMPTY_INSTANCE,
|
||||
((FlowExecutionImplFactory)flowExecutor.getExecutionFactory()).getExecutionListenerLoader());
|
||||
}
|
||||
|
||||
public void testDuplicateRepositoryType() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
|
||||
try {
|
||||
reader.loadBeanDefinitions(new ClassPathResource("org/springframework/webflow/config/namespace-error-1.xml"));
|
||||
fail("Should have thrown an BeanDefinitionStoreException exception");
|
||||
} catch (BeanDefinitionStoreException e) {}
|
||||
}
|
||||
|
||||
public void testConversationManagerRef() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
|
||||
try {
|
||||
reader.loadBeanDefinitions(new ClassPathResource("org/springframework/webflow/config/namespace-error-2.xml"));
|
||||
fail("Should have thrown a BeanDefinitionStoreException exception");
|
||||
} catch (BeanDefinitionStoreException e) {}
|
||||
}
|
||||
|
||||
public void testMaxContinuation() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
|
||||
try {
|
||||
reader.loadBeanDefinitions(new ClassPathResource("org/springframework/webflow/config/namespace-error-3.xml"));
|
||||
fail("Should have thrown a BeanDefinitionStoreException exception");
|
||||
} catch (BeanDefinitionStoreException e) {}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:flow="http://www.springframework.org/schema/webflow-config"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
|
||||
http://www.springframework.org/schema/webflow-config
|
||||
http://www.springframework.org/schema/webflow-config/spring-webflow-config-1.0.xsd">
|
||||
|
||||
<flow:registry id="withPath">
|
||||
<flow:location path="classpath:org/springframework/webflow/registry/flow1.xml" />
|
||||
</flow:registry>
|
||||
|
||||
<flow:executor id="errorExecutor" repository-type="continuation" registry-ref="withPath">
|
||||
<flow:repository type="continuation" />
|
||||
</flow:executor>
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:flow="http://www.springframework.org/schema/webflow-config"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
|
||||
http://www.springframework.org/schema/webflow-config
|
||||
http://www.springframework.org/schema/webflow-config/spring-webflow-config-1.0.xsd">
|
||||
|
||||
<flow:registry id="withPath">
|
||||
<flow:location path="classpath:org/springframework/webflow/registry/flow1.xml" />
|
||||
</flow:registry>
|
||||
|
||||
<flow:executor id="errorExecutor" registry-ref="withPath">
|
||||
<flow:repository type="simple" max-conversations="10"
|
||||
conversation-manager-ref="conversationManager" />
|
||||
</flow:executor>
|
||||
|
||||
<bean id="conversationManager"
|
||||
class="org.springframework.webflow.conversation.impl.SessionBindingConversationManager"/>
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:flow="http://www.springframework.org/schema/webflow-config"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
|
||||
http://www.springframework.org/schema/webflow-config
|
||||
http://www.springframework.org/schema/webflow-config/spring-webflow-config-1.0.xsd">
|
||||
|
||||
<flow:registry id="withPath">
|
||||
<flow:location path="classpath:org/springframework/webflow/registry/flow1.xml" />
|
||||
</flow:registry>
|
||||
|
||||
<flow:executor id="errorExecutor" registry-ref="withPath">
|
||||
<flow:repository type="simple" max-continuations="25" />
|
||||
</flow:executor>
|
||||
|
||||
</beans>
|
||||
Reference in New Issue
Block a user