Updated the config schema and bean definition parser to accomodate changes from SWF-210.

This commit is contained in:
Ben Hale
2006-12-22 15:57:09 +00:00
parent f7d3aa49ca
commit 1c4fccf76f
6 changed files with 293 additions and 50 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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