Index: src/main/java/META-INF/spring.schemas
===================================================================
--- src/main/java/META-INF/spring.schemas (revision 7805)
+++ src/main/java/META-INF/spring.schemas (working copy)
@@ -1,2 +1,2 @@
http\://www.springframework.org/schema/webflow-config/spring-webflow-config-1.0.xsd=org/springframework/webflow/config/spring-webflow-config-1.0.xsd
-http\://www.springframework.org/schema/webflow-config/spring-webflow-config-1.1.xsd=org/springframework/webflow/config/spring-webflow-config-1.1.xsd
\ No newline at end of file
+http\://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd=org/springframework/webflow/config/spring-webflow-config-2.0.xsd
\ No newline at end of file
Index: src/main/java/org/springframework/webflow/config/RegistryBeanDefinitionParser.java
===================================================================
--- src/main/java/org/springframework/webflow/config/RegistryBeanDefinitionParser.java (revision 7805)
+++ src/main/java/org/springframework/webflow/config/RegistryBeanDefinitionParser.java (working copy)
@@ -15,6 +15,7 @@
*/
package org.springframework.webflow.config;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -19,11 +20,18 @@
import java.util.Iterator;
import java.util.List;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
-import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
+import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternUtils;
+import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
+import org.springframework.webflow.definition.registry.NamespaceMapping;
import org.springframework.webflow.engine.builder.xml.XmlFlowRegistryFactoryBean;
import org.w3c.dom.Element;
@@ -32,7 +40,7 @@
*
* @author Ben Hale
*/
-class RegistryBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
+class RegistryBeanDefinitionParser extends AbstractBeanDefinitionParser {
// elements and attributes
@@ -38,6 +46,12 @@
private static final String LOCATION_ELEMENT = "location";
+ private static final String NAMESPACE_ELEMENT = "namespace";
+
+ private static final String NAME_ATTRIBUTE = "name";
+
+ private static final String PATH_ATTRIBUTE = "path";
+
// properties
private static final String FLOW_LOCATIONS_PROPERTY = "flowLocations";
@@ -42,22 +56,54 @@
private static final String FLOW_LOCATIONS_PROPERTY = "flowLocations";
- private static final String PATH_ATTRIBUTE = "path";
+ private static final String FLOW_NAMESPACE_MAPPINGS_PROPERTY = "flowNamespaceMappings";
+
+ protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
+ BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder
+ .rootBeanDefinition(XmlFlowRegistryFactoryBean.class);
+ definitionBuilder.setSource(parserContext.extractSource(element));
+ addNamespaceMappings(element, parserContext, definitionBuilder);
+ addLocations(element, parserContext, definitionBuilder);
- protected Class getBeanClass(Element element) {
- return XmlFlowRegistryFactoryBean.class;
+ return definitionBuilder.getBeanDefinition();
}
- protected void doParse(Element element, BeanDefinitionBuilder definitionBuilder) {
+ private void addLocations(Element element, ParserContext parserContext, BeanDefinitionBuilder definitionBuilder) {
List locationElements = DomUtils.getChildElementsByTagName(element, LOCATION_ELEMENT);
- List locations = getLocations(locationElements);
- definitionBuilder.addPropertyValue(FLOW_LOCATIONS_PROPERTY, locations.toArray(new String[locations.size()]));
+ definitionBuilder.addPropertyValue(FLOW_LOCATIONS_PROPERTY, getLocations(locationElements, parserContext));
+ }
+
+ private void addNamespaceMappings(Element element, ParserContext parserContext,
+ BeanDefinitionBuilder definitionBuilder) {
+ List namespaceElements = DomUtils.getChildElementsByTagName(element, NAMESPACE_ELEMENT);
+ definitionBuilder.addPropertyValue(FLOW_NAMESPACE_MAPPINGS_PROPERTY, getNamespaceMappings(namespaceElements,
+ parserContext));
}
/**
- * Parse location definitions from given list of location elements.
+ * Parsers namespace mappings from a given list of namespace elements.
*/
- private List getLocations(List locationElements) {
+ private NamespaceMapping[] getNamespaceMappings(List namespaceElements, ParserContext parserContext) {
+ List namespaceMappings = new ArrayList(namespaceElements.size());
+ for (Iterator it = namespaceElements.iterator(); it.hasNext();) {
+ Element namespaceElement = (Element) it.next();
+ String name = namespaceElement.getAttribute(NAME_ATTRIBUTE);
+ if (!StringUtils.hasText(name)) {
+ parserContext.getReaderContext().error(
+ "The 'name' attribute of the 'namespace' element must have a value", namespaceElement);
+ }
+ List locationElements = DomUtils.getChildElementsByTagName(namespaceElement, LOCATION_ELEMENT);
+ namespaceMappings.add(new NamespaceMapping(name, getLocations(locationElements, parserContext)));
+ }
+ return (NamespaceMapping[]) namespaceMappings.toArray(new NamespaceMapping[namespaceMappings.size()]);
+ }
+
+ /**
+ * Parse location resources from given list of location elements.
+ */
+ private Resource[] getLocations(List locationElements, ParserContext parserContext) {
+ ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(parserContext
+ .getReaderContext().getResourceLoader());
List locations = new ArrayList(locationElements.size());
for (Iterator i = locationElements.iterator(); i.hasNext();) {
Element locationElement = (Element) i.next();
@@ -63,9 +109,14 @@
Element locationElement = (Element) i.next();
String path = locationElement.getAttribute(PATH_ATTRIBUTE);
if (StringUtils.hasText(path)) {
- locations.add(path);
+ try {
+ CollectionUtils.mergeArrayIntoCollection(resolver.getResources(path), locations);
+ } catch (IOException e) {
+ parserContext.getReaderContext().error("Could not find a flow definition at path '" + path + "'",
+ locationElement);
+ }
}
}
- return locations;
+ return (Resource[]) locations.toArray(new Resource[locations.size()]);
}
}
\ No newline at end of file
Index: src/main/java/org/springframework/webflow/config/spring-webflow-config-1.1.xsd
===================================================================
--- src/main/java/org/springframework/webflow/config/spring-webflow-config-1.1.xsd (revision 7805)
+++ src/main/java/org/springframework/webflow/config/spring-webflow-config-1.1.xsd (working copy)
@@ -1,356 +0,0 @@
-
-
-A flow registry is used by a flow executor at runtime to launch new executions of flow definitions.
-]]>
-
- /WEB-INF/flows/orderitem-flow.xml
-
-... are supported as well as wildcard paths such as:
-
- /WEB-INF/flows/**/*-flow.xml
-
-]]>
-
+A flow registry is used by a flow executor at runtime to launch new executions of flow definitions.
+]]>
+
+ /WEB-INF/flows/orderitem-flow.xml
+
+... are supported as well as wildcard paths such as:
+
+ /WEB-INF/flows/**/*-flow.xml
+
+]]>
+
+ /WEB-INF/flows/orderitem-flow.xml
+
+... are supported as well as wildcard paths such as:
+
+ /WEB-INF/flows/**/*-flow.xml
+
+]]>
+
+ /WEB-INF/flows/orderitem-flow.xml
+
+... are supported as well as wildcard paths such as:
+
+ /WEB-INF/flows/**/*-flow.xml
+
+]]>
+
* Flows registered from this set will be automatically assigned an id based on the filename of the flow resource. @@ -79,6 +86,14 @@ } /** + * Sets the mappings between a namespace and a set of externalized flow definitions. + * @param namespaceMappings the mapping between a namespace and a set of externalized flow definitions + */ + public void setNamespaceMappings(NamespaceMapping[] namespaceMappings) { + this.namespaceMappings = new HashSet(Arrays.asList(namespaceMappings)); + } + + /** * Adds a flow location pointing to an externalized flow resource. *
* The flow registered from this location will automatically assigned an id based on the filename of the flow @@ -128,9 +143,29 @@ return this.resources.addAll(Arrays.asList(resources)); } + /** + * Adds a mapping between a namespace and a set of externalized flow definitions. + * @param namespaceMapping the mapping between a namespace and a set of externalized flow definitions + */ + public boolean addNamespaceMapping(NamespaceMapping namespaceMapping) { + return namespaceMappings.add(namespaceMapping); + } + + /** + * Adds mappings between a namespace and a set of externalized flow definitions. + * @param namespaceMappings the mappings between a namespace and a set of externalized flow definitions + */ + public boolean addNamespaceMappings(NamespaceMapping[] namespaceMappings) { + if (namespaceMappings == null) { + return false; + } + return this.namespaceMappings.addAll(Arrays.asList(namespaceMappings)); + } + public void registerFlowDefinitions(FlowDefinitionRegistry registry) { - processLocations(registry); - processResources(registry); + processNamespaceMappings(namespaceMappings, registry); + processLocations(locations, "", registry); + processResources(resources, "", registry); } // internal helpers @@ -136,16 +171,30 @@ // internal helpers /** + * Register the flow definitions in the namespace mappings. + * @param namespaceMappings The namespace mapping to traverse + * @param registry the registry + */ + private void processNamespaceMappings(Set namespaceMappings, FlowDefinitionRegistry registry) { + for (Iterator it = namespaceMappings.iterator(); it.hasNext();) { + NamespaceMapping namespaceMapping = (NamespaceMapping) it.next(); + processLocations(namespaceMapping.getLocations(), namespaceMapping.getNamespace(), registry); + processResources(namespaceMapping.getResources(), namespaceMapping.getNamespace(), registry); + } + } + + /** * Register the flow definitions at the configured file locations. + * @param locations the set of locations + * @param the namespace to register the flow definitions in * @param registry the registry */ - private void processLocations(FlowDefinitionRegistry registry) { - Iterator it = locations.iterator(); - while (it.hasNext()) { + private void processLocations(Set locations, String namespace, FlowDefinitionRegistry registry) { + for (Iterator it = locations.iterator(); it.hasNext();) { Resource location = (Resource) it.next(); if (isFlowDefinitionResource(location)) { FlowDefinitionResource resource = createFlowDefinitionResource(location); - register(resource, registry); + register(resource, namespace, registry); } } } @@ -152,13 +201,14 @@ /** * Register the flow definitions at the configured file locations. + * @param resources the set of resources + * @param the namespace to register the flow definitions in * @param registry the registry */ - private void processResources(FlowDefinitionRegistry registry) { - Iterator it = resources.iterator(); - while (it.hasNext()) { + private void processResources(Set resources, String namespace, FlowDefinitionRegistry registry) { + for (Iterator it = resources.iterator(); it.hasNext();) { FlowDefinitionResource resource = (FlowDefinitionResource) it.next(); - register(resource, registry); + register(resource, namespace, registry); } } @@ -165,10 +215,11 @@ /** * Helper method to register the flow built from an externalized resource in the registry. * @param resource representation of the externalized flow definition resource + * @param namespace the namespace to register the flow in * @param registry the flow registry to register the flow in */ - protected final void register(FlowDefinitionResource resource, FlowDefinitionRegistry registry) { - registry.registerFlowDefinition(createFlowDefinitionHolder(resource)); + protected final void register(FlowDefinitionResource resource, String namespace, FlowDefinitionRegistry registry) { + registry.registerFlowDefinition(createFlowDefinitionHolder(resource), namespace); } // subclassing hooks Index: src/main/java/org/springframework/webflow/definition/registry/NamespaceMapping.java =================================================================== --- src/main/java/org/springframework/webflow/definition/registry/NamespaceMapping.java (revision 0) +++ src/main/java/org/springframework/webflow/definition/registry/NamespaceMapping.java (revision 0) @@ -0,0 +1,163 @@ +/* + * Copyright 2004-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.webflow.definition.registry; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.core.io.Resource; + +/** + * A helper class that contains a mapping from a namespace to a set of locations or resources. + * + * @author Ben Hale + */ +public class NamespaceMapping { + + /** + * The namespace to map all flows to. + */ + private String namespace; + + /** + * File locations of externalized flow definition resources to load. A set of {@link Resource} objects. + */ + private Set locations = new HashSet(); + + /** + * A set of formal externalized flow definitions to load. A set of {@link FlowDefinitionResource} objects. + */ + private Set resources = new HashSet(); + + public NamespaceMapping() { + } + + public NamespaceMapping(String namespace, Resource location) { + this.namespace = namespace; + this.locations.add(location); + } + + public NamespaceMapping(String namespace, Resource[] locations) { + this.namespace = namespace; + setLocations(locations); + } + + public NamespaceMapping(String namespace, FlowDefinitionResource resource) { + this.namespace = namespace; + this.resources.add(resource); + } + + /** + * Returns the namespace to map all flows into. + */ + public String getNamespace() { + return namespace; + } + + /** + * Sets the namespace to map all flows into. + * @param namespace the namespace name + */ + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + /** + * Returns the locations (file paths) pointing to externalized flow definitions. + */ + public Set getLocations() { + return locations; + } + + /** + * Sets the locations (file paths) pointing to externalized flow definitions. + *
+ * Flows registered from this set will be automatically assigned an id based on the filename of the flow resource. + * @param locations the resource locations + */ + public void setLocations(Resource[] locations) { + this.locations = new HashSet(Arrays.asList(locations)); + } + + /** + * Returns the set of externalized flow definitions. + */ + public Set getResources() { + return resources; + } + + /** + * Sets the formal set of externalized flow definitions this registrar will register. + *
+ * Use this method when you want full control over the assigned flow id and the set of properties applied to the + * externalized flow resources. + * @param resources the externalized flow definition specifications + */ + public void setResources(FlowDefinitionResource[] resources) { + this.resources = new HashSet(Arrays.asList(resources)); + } + + /** + * Adds a flow location pointing to an externalized flow resource. + *
+ * The flow registered from this location will automatically assigned an id based on the filename of the flow + * resource. + * @param location the definition location + */ + public boolean addLocation(Resource location) { + return locations.add(location); + } + + /** + * Adds the flow locations pointing to externalized flow resources. + *
+ * The flow registered from this location will automatically assigned an id based on the filename of the flow + * resource. + * @param locations the definition locations + */ + public boolean addLocations(Resource[] locations) { + if (locations == null) { + return false; + } + return this.locations.addAll(Arrays.asList(locations)); + } + + /** + * Adds an externalized flow definition specification pointing to an externalized flow resource. + *
+ * Use this method when you want full control over the assigned flow id and the set of properties applied to the + * externalized flow resource. + * @param resource the definition the definition resource + */ + public boolean addResource(FlowDefinitionResource resource) { + return resources.add(resource); + } + + /** + * Adds the externalized flow definitions pointing to externalized flow resources. + *
+ * Use this method when you want full control over the assigned flow id and the set of properties applied to the + * externalized flow resources. + * @param resources the definitions + */ + public boolean addResources(FlowDefinitionResource[] resources) { + if (resources == null) { + return false; + } + return this.resources.addAll(Arrays.asList(resources)); + } +} Index: src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowRegistrar.java =================================================================== --- src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowRegistrar.java (revision 7805) +++ src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowRegistrar.java (working copy) @@ -68,8 +68,9 @@ private DocumentLoader documentLoader; /** - * Creates a new XML flow registrar. Protected constructor - if used, make sure the required - * {@link #flowServiceLocator} reference is set. + * Creates a new XML flow registrar. Protected constructor - if used, make sure the + * {@link #setFlowServiceLocator(FlowServiceLocator)} method is called to set the required flowServiceLocator + * reference. */ protected XmlFlowRegistrar() { } Index: src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowRegistryFactoryBean.java =================================================================== --- src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowRegistryFactoryBean.java (revision 7805) +++ src/main/java/org/springframework/webflow/engine/builder/xml/XmlFlowRegistryFactoryBean.java (working copy) @@ -25,6 +25,7 @@ import org.springframework.webflow.core.collection.LocalAttributeMap; import org.springframework.webflow.definition.registry.FlowDefinitionRegistry; import org.springframework.webflow.definition.registry.FlowDefinitionResource; +import org.springframework.webflow.definition.registry.NamespaceMapping; import org.springframework.webflow.engine.builder.AbstractFlowBuildingFlowRegistryFactoryBean; import org.springframework.webflow.engine.builder.DefaultFlowServiceLocator; import org.springframework.webflow.engine.builder.FlowServiceLocator; @@ -56,6 +57,7 @@ * * * @author Keith Donald + * @author Ben Hale */ public class XmlFlowRegistryFactoryBean extends AbstractFlowBuildingFlowRegistryFactoryBean { @@ -70,6 +72,11 @@ private Resource[] locations; /** + * Temporary holder for namespace mappings. + */ + private NamespaceMapping[] namespaceMappings; + + /** * Temporary holder for flow definitions configured using a property map. */ private Properties flowDefinitions; @@ -118,7 +125,7 @@ * * * Flows registered from this set will be automatically assigned an id based on the filename of the matched XML - * resource. + * resource and be registered in the root namespace. * @param locations the resource locations */ public void setFlowLocations(Resource[] locations) { @@ -126,6 +133,17 @@ } /** + * Sets the namespace mappings (namespace name to resource file paths) pointing to XML-based flow definitions. + *
+ * Flows registered from this set will be automatically assigned an id based on the filename of the matched XML
+ * resource and be registered in the declared namespace.
+ * @param namespaceMappings the namespace mappings
+ */
+ public void setFlowNamespaceMappings(NamespaceMapping[] namespaceMappings) {
+ this.namespaceMappings = namespaceMappings;
+ }
+
+ /**
* Convenience method for setting externalized flow definitions from a java.util.Properties
map.
* Allows for more control over the definition, including which flowId
is assigned.
*
@@ -189,6 +207,7 @@
protected void doPopulate(FlowDefinitionRegistry registry) {
addFlowDefinitionLocations();
addFlowDefinitionsFromProperties();
+ addNamespaceMappings();
getXmlFlowRegistrar().registerFlowDefinitions(registry);
}
@@ -197,6 +216,7 @@
*/
private void addFlowDefinitionLocations() {
if (locations != null) {
+
for (int i = 0; i < locations.length; i++) {
String flowId = FlowDefinitionResource.conventionalFlowId(locations[i]);
getXmlFlowRegistrar().addResource(
@@ -223,6 +243,15 @@
}
/**
+ * Add flow namespace mappings to the flow definition registrar.
+ */
+ private void addNamespaceMappings() {
+ if (namespaceMappings != null) {
+ getXmlFlowRegistrar().setNamespaceMappings(namespaceMappings);
+ }
+ }
+
+ /**
* Returns the flow attributes to be assigned to the flow with given id. Returns null if no attributes should be
* assigned.
*/
Index: src/test/java/org/springframework/webflow/config/webflow-config-namespace.xml
===================================================================
--- src/test/java/org/springframework/webflow/config/webflow-config-namespace.xml (revision 7805)
+++ src/test/java/org/springframework/webflow/config/webflow-config-namespace.xml (working copy)
@@ -6,7 +6,7 @@
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">
+ http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd">