diff --git a/spring-webflow/changelog.txt b/spring-webflow/changelog.txt index b19e0462..eaba990c 100644 --- a/spring-webflow/changelog.txt +++ b/spring-webflow/changelog.txt @@ -30,6 +30,10 @@ Package org.springframework.webflow.core * Fixed DefaultExpressionParserFactory to not require OGNL on the classpath if another expression parser is configured (SWF-335). +Package org.springframework.webflow.definition.registry +* Added namespace awareness to the flow registry allowing flows with the same id to be placed in different namespaces + and be referenced by a 'flow path' (SWF-363). + Package org.springframework.webflow.engine * Added invoke(String, Action) method to AbstractFlowBuilder. * Added initBuilder() hook method to AbstractFlowBuilder. diff --git a/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowDefinitionHolder.java b/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowDefinitionHolder.java index 06d11bdf..2f179eb0 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowDefinitionHolder.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowDefinitionHolder.java @@ -18,8 +18,8 @@ package org.springframework.webflow.definition.registry; import org.springframework.webflow.definition.FlowDefinition; /** - * A holder holding a reference to a Flow definition. Provides a layer of - * indirection, enabling things like "hot-reloadable" flow definitions. + * A holder holding a reference to a Flow definition. Provides a layer of indirection, enabling things like + * "hot-reloadable" flow definitions. * * @see FlowDefinitionRegistry#registerFlowDefinition(FlowDefinitionHolder) * @@ -28,27 +28,23 @@ import org.springframework.webflow.definition.FlowDefinition; public interface FlowDefinitionHolder { /** - * Returns the id of the flow definition 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). + * Returns the id of the flow definition 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 getFlowDefinitionId(); /** - * Returns the flow definition held by this holder. Calling this method the - * first time may trigger flow assembly (which may be expensive). - * @throws FlowDefinitionConstructionException if there is a problem constructing - * the target flow definition + * Returns the flow definition held by this holder. Calling this method the first time may trigger flow assembly + * (which may be expensive). + * @throws FlowDefinitionConstructionException if there is a problem constructing the target flow definition */ public FlowDefinition getFlowDefinition() throws FlowDefinitionConstructionException; /** - * Refresh the flow definition held by this holder. Calling this method - * typically triggers flow reassembly, which may include a refresh from an - * externalized resource such as a file. - * @throws FlowDefinitionConstructionException if there is a problem constructing - * the target flow definition + * Refresh the flow definition 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 FlowDefinitionConstructionException if there is a problem constructing the target flow definition */ public void refresh() throws FlowDefinitionConstructionException; } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowDefinitionLocator.java b/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowDefinitionLocator.java index ccefa49f..b93b21b3 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowDefinitionLocator.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowDefinitionLocator.java @@ -18,11 +18,10 @@ package org.springframework.webflow.definition.registry; import org.springframework.webflow.definition.FlowDefinition; /** - * A runtime service locator interface for retrieving flow definitions by - * id. + * A runtime service locator interface for retrieving flow definitions by id. *

- * Flow locators are needed by flow executors at runtime to retrieve - * fully-configured flow definitions to support launching new flow executions. + * Flow locators are needed by flow executors at runtime to retrieve fully-configured flow definitions to support + * launching new flow executions. * * @author Keith Donald * @author Erwin Vervaet @@ -30,14 +29,12 @@ import org.springframework.webflow.definition.FlowDefinition; public interface FlowDefinitionLocator { /** - * Lookup the flow definition with the specified id. - * @param id the flow definition id + * Lookup the flow definition with the specified path. + * @param flowPath the flow definition path * @return the flow definition - * @throws NoSuchFlowDefinitionException when the flow definition with the - * specified id does not exist - * @throws FlowDefinitionConstructionException if there is a problem constructing - * the identified flow definition + * @throws NoSuchFlowDefinitionException when the flow definition with the specified id does not exist + * @throws FlowDefinitionConstructionException if there is a problem constructing the identified flow definition */ - public FlowDefinition getFlowDefinition(String id) - throws NoSuchFlowDefinitionException, FlowDefinitionConstructionException; + public FlowDefinition getFlowDefinition(String flowPath) throws NoSuchFlowDefinitionException, + FlowDefinitionConstructionException; } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowDefinitionRegistry.java b/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowDefinitionRegistry.java index 4a7eb360..40950e1f 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowDefinitionRegistry.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowDefinitionRegistry.java @@ -15,7 +15,6 @@ */ package org.springframework.webflow.definition.registry; -import org.springframework.webflow.definition.FlowDefinition; /** * A container of flow definitions. Extends the {@link FlowDefinitionRegistryMBean} management interface exposing @@ -26,6 +25,7 @@ import org.springframework.webflow.definition.FlowDefinition; * registry hierarchy. * * @author Keith Donald + * @author Ben Hale */ public interface FlowDefinitionRegistry extends FlowDefinitionLocator, FlowDefinitionRegistryMBean { @@ -36,21 +36,21 @@ public interface FlowDefinitionRegistry extends FlowDefinitionLocator, FlowDefin */ public void setParent(FlowDefinitionRegistry parent); - /** - * Return all flow definitions registered in this registry. Note that this will trigger flow assembly for all - * registered flow definitions (which may be expensive). - * @return the flow definitions - * @throws FlowDefinitionConstructionException if there is a problem constructing one of the registered flow - * definitions - */ - public FlowDefinition[] getFlowDefinitions() throws FlowDefinitionConstructionException; - /** * Register a flow definition in this registry. Registers a "holder", not the Flow definition itself. This allows * the actual Flow definition to be loaded lazily only when needed, and also rebuilt at runtime when its underlying - * resource changes without redeploy. + * resource changes without re-deploy. * @param flowHolder a holder holding the flow definition to register */ public void registerFlowDefinition(FlowDefinitionHolder flowHolder); + /** + * Register a flow definition in this registry under a specific namespace. Registers a "holder", not the Flow + * definition itself. This allows the actual Flow definition to be loaded lazily only when needed, and also rebuilt + * at runtime when its underlying resource changes without re-deploy. + * @param flowHolder a holder holding the flow definition to register + * @param namespace the namespace to register the flow definition in + */ + public void registerFlowDefinition(FlowDefinitionHolder flowHolder, String namespace); + } \ No newline at end of file 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 3c807752..9f12b525 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 @@ -17,6 +17,7 @@ package org.springframework.webflow.definition.registry; import java.util.Iterator; import java.util.LinkedList; +import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -34,200 +35,192 @@ import org.springframework.webflow.definition.FlowDefinition; * definitions that no longer exist. * * @author Keith Donald + * @author Ben Hale */ public class FlowDefinitionRegistryImpl implements FlowDefinitionRegistry { - private static final Log logger = LogFactory.getLog(FlowDefinitionRegistryImpl.class); + private static final Log logger = LogFactory.getLog(FlowDefinitionRegistryImpl.class); - /** - * The map of loaded Flow definitions maintained in this registry. - */ - private Map flowDefinitions; + /** + * The map of loaded Flow definitions maintained in this registry. + */ + private Map flowDefinitions; - /** - * An optional parent flow definition registry. - */ - private FlowDefinitionRegistry parent; + /** + * An optional parent flow definition registry. + */ + private FlowDefinitionRegistry parent; - public FlowDefinitionRegistryImpl() { - flowDefinitions = new TreeMap(); - } - - // implementing FlowDefinitionRegistryMBean - - public String[] getFlowDefinitionIds() { - return (String[]) flowDefinitions.keySet().toArray(new String[flowDefinitions.size()]); - } - - public int getFlowDefinitionCount() { - return flowDefinitions.size(); - } - - public boolean containsFlowDefinition(String id) { - Assert.hasText(id, "The flow id is required"); - return flowDefinitions.get(id) != null; - } - - public void refresh() throws FlowDefinitionConstructionException { - if (logger.isDebugEnabled()) { - logger.debug("Refreshing flow definition registry '" + this + "'"); + public FlowDefinitionRegistryImpl() { + flowDefinitions = new TreeMap(); } - ClassLoader loader = Thread.currentThread().getContextClassLoader(); - try { - // workaround for JMX - Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); - LinkedList needsReindexing = new LinkedList(); - Iterator it = flowDefinitions.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry entry = (Map.Entry) it.next(); - String key = (String) entry.getKey(); - FlowDefinitionHolder holder = (FlowDefinitionHolder) entry.getValue(); - holder.refresh(); - if (!holder.getFlowDefinitionId().equals(key)) { - needsReindexing.add(new Indexed(key, holder)); + + // implementing FlowDefinitionRegistryMBean + + public String[] getFlowDefinitionPaths() { + List flowPaths = new LinkedList(); + for (Iterator namespaces = flowDefinitions.entrySet().iterator(); namespaces.hasNext();) { + Map.Entry namespaceEntry = (Map.Entry) namespaces.next(); + String namespaceName = (String) namespaceEntry.getKey(); + Map namespace = (Map) namespaceEntry.getValue(); + for (Iterator ids = namespace.keySet().iterator(); ids.hasNext();) { + flowPaths.add(FlowPathUtils.buildFlowPath(namespaceName, (String) ids.next())); + } } - } - it = needsReindexing.iterator(); - while (it.hasNext()) { - Indexed indexed = (Indexed) it.next(); - reindex(indexed.holder, indexed.key); - } - } finally { - Thread.currentThread().setContextClassLoader(loader); + return (String[]) flowPaths.toArray(new String[flowPaths.size()]); } - } - public void refresh(String flowId) throws NoSuchFlowDefinitionException, FlowDefinitionConstructionException { - if (logger.isDebugEnabled()) { - logger.debug("Refreshing flow with id '" + flowId + "'"); + public int getFlowDefinitionCount() { + int count = 0; + for (Iterator namespaces = flowDefinitions.values().iterator(); namespaces.hasNext();) { + Map namespace = (Map) namespaces.next(); + count += namespace.size(); + } + return count; } - ClassLoader loader = Thread.currentThread().getContextClassLoader(); - try { - // workaround for JMX - Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); - FlowDefinitionHolder holder = getFlowDefinitionHolder(flowId); - holder.refresh(); - if (!holder.getFlowDefinitionId().equals(flowId)) { - reindex(holder, flowId); - } - } finally { - Thread.currentThread().setContextClassLoader(loader); + + public boolean containsFlowDefinition(String flowPath) { + Assert.hasText(flowPath, "The flow path is required"); + Map namespace = getNamespace(FlowPathUtils.extractFlowNamespace(flowPath)); + return namespace.containsKey(FlowPathUtils.extractFlowId(flowPath)); } - } - // implementing FlowDefinitionLocator - - public FlowDefinition getFlowDefinition(String id) throws NoSuchFlowDefinitionException, - FlowDefinitionConstructionException { - Assert.hasText(id, - "Unable to load a flow definition: no flow id was provided. Please provide a valid flow identifier."); - if (logger.isDebugEnabled()) { - logger.debug("Getting flow definition with id '" + id + "'"); + public void refresh() throws FlowDefinitionConstructionException { + if (logger.isDebugEnabled()) { + logger.debug("Refreshing flow definition registry '" + this + "'"); + } + for (Iterator namespaces = flowDefinitions.entrySet().iterator(); namespaces.hasNext();) { + Map.Entry namespaceEntry = (Map.Entry) namespaces.next(); + String namespaceName = (String) namespaceEntry.getKey(); + Map namespace = (Map) namespaceEntry.getValue(); + for (Iterator ids = namespace.keySet().iterator(); ids.hasNext();) { + refresh(FlowPathUtils.buildFlowPath(namespaceName, (String) ids.next())); + } + } } - try { - return getFlowDefinitionHolder(id).getFlowDefinition(); - } catch (NoSuchFlowDefinitionException e) { - if (parent != null) { - // try parent - return parent.getFlowDefinition(id); - } - throw e; + + public void refresh(String flowPath) throws NoSuchFlowDefinitionException, FlowDefinitionConstructionException { + if (logger.isDebugEnabled()) { + logger.debug("Refreshing flow with path '" + flowPath + "'"); + } + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + try { + // workaround for JMX + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + FlowDefinitionHolder holder = getFlowDefinitionHolder(flowPath); + holder.refresh(); + if (!holder.getFlowDefinitionId().equals(FlowPathUtils.extractFlowId(flowPath))) { + reindex(holder, FlowPathUtils.extractFlowNamespace(flowPath), flowPath); + } + } finally { + Thread.currentThread().setContextClassLoader(loader); + } } - } - // implementing FlowDefinitionRegistry + // implementing FlowDefinitionLocator - public void setParent(FlowDefinitionRegistry parent) { - if (logger.isDebugEnabled()) { - logger.debug("Setting parent flow definition registry to '" + parent + "'"); + public FlowDefinition getFlowDefinition(String path) throws NoSuchFlowDefinitionException, + FlowDefinitionConstructionException { + Assert.hasText(path, + "Unable to load a flow definition: no flow path was provided. Please provide a valid flow path."); + if (logger.isDebugEnabled()) { + logger.debug("Getting flow definition with path '" + path + "'"); + } + try { + return getFlowDefinitionHolder(path).getFlowDefinition(); + } catch (NoSuchFlowDefinitionException e) { + if (parent != null) { + // try parent + return parent.getFlowDefinition(path); + } + throw e; + } } - this.parent = parent; - } - public FlowDefinition[] getFlowDefinitions() throws FlowDefinitionConstructionException { - FlowDefinition[] flows = new FlowDefinition[flowDefinitions.size()]; - Iterator it = flowDefinitions.values().iterator(); - int i = 0; - while (it.hasNext()) { - FlowDefinitionHolder holder = (FlowDefinitionHolder) it.next(); - flows[i] = holder.getFlowDefinition(); - i++; + // implementing FlowDefinitionRegistry + + public void setParent(FlowDefinitionRegistry parent) { + if (logger.isDebugEnabled()) { + logger.debug("Setting parent flow definition registry to '" + parent + "'"); + } + this.parent = parent; } - return flows; - } - public void registerFlowDefinition(FlowDefinitionHolder flowHolder) { - Assert.notNull(flowHolder, "The flow definition holder to register is required"); - if (logger.isDebugEnabled()) { - logger.debug("Registering flow definition with id '" + flowHolder.getFlowDefinitionId() + "'"); + public void registerFlowDefinition(FlowDefinitionHolder flowHolder) { + registerFlowDefinition(flowHolder, ""); } - index(flowHolder); - } - /** - * Remove identified flow definition from this registry. If the given id is not known in this registry, nothing will - * happen. - * @param id the flow definition id - */ - public void removeFlowDefinition(String id) { - Assert.hasText(id, "The flow id is required"); - if (logger.isDebugEnabled()) { - logger.debug("Removing flow definition with id '" + id + "'"); + public void registerFlowDefinition(FlowDefinitionHolder flowHolder, String namespace) { + Assert.notNull(flowHolder, "The flow definition holder to register is required"); + Assert.notNull(namespace, "The flow namespace is required"); + if (logger.isDebugEnabled()) { + logger.debug("Registering flow definition with id '" + flowHolder.getFlowDefinitionId() + + "' in namespace '" + namespace + "'"); + } + index(flowHolder, namespace); } - flowDefinitions.remove(id); - } - // internal helpers - - /** - * Reindex given flow definition. - * @param holder the holder holding the flow definition to reindex - * @param oldId the id that was previously assigned to given flow definition - */ - private void reindex(FlowDefinitionHolder holder, String oldId) { - flowDefinitions.remove(oldId); - index(holder); - } - - /** - * Index given flow definition. - * @param holder the holder holding the flow definition to index - */ - private void index(FlowDefinitionHolder holder) { - Assert.hasText(holder.getFlowDefinitionId(), "The flow holder to index must return a non-blank flow id"); - flowDefinitions.put(holder.getFlowDefinitionId(), holder); - } - - /** - * Returns the identified flow definition holder. Throws an exception if it cannot be found. - */ - private FlowDefinitionHolder getFlowDefinitionHolder(String id) throws NoSuchFlowDefinitionException { - FlowDefinitionHolder flowHolder = (FlowDefinitionHolder) flowDefinitions.get(id); - if (flowHolder == null) { - throw new NoSuchFlowDefinitionException(id, getFlowDefinitionIds()); + /** + * Remove identified flow definition from this registry. If the given id is not known in this registry, nothing will + * happen. + * @param flowPath the flow definition path + */ + public void removeFlowDefinition(String flowPath) { + Assert.hasText(flowPath, "The flow path is required"); + if (logger.isDebugEnabled()) { + logger.debug("Removing flow definition with path '" + flowPath + "'"); + } + Map namespace = getNamespace(FlowPathUtils.extractFlowNamespace(flowPath)); + namespace.remove(FlowPathUtils.extractFlowId(flowPath)); } - return flowHolder; - } - /** - * Simple value object that holds the key for an indexed flow definition holder in this registry. Used to support - * reindexing on a refresh. - * - * @author Keith Donald - */ - private static class Indexed { + // internal helpers - private String key; - - private FlowDefinitionHolder holder; - - public Indexed(String key, FlowDefinitionHolder holder) { - this.key = key; - this.holder = holder; + /** + * Re-index given flow definition. + * @param holder the holder holding the flow definition to re-index + * @param namespace the namespace to index the new flow in + * @param oldFlowPath the flowPath that was previously assigned to given flow definition + */ + private void reindex(FlowDefinitionHolder holder, String namespace, String oldFlowPath) { + removeFlowDefinition(oldFlowPath); + index(holder, namespace); } - } - public String toString() { - return new ToStringCreator(this).append("flowDefinitions", flowDefinitions).append("parent", parent).toString(); - } + /** + * Index given flow definition. + * @param holder the holder holding the flow definition to index + * @param namespaceName the namespace to index the flow definition in + */ + private void index(FlowDefinitionHolder holder, String namespaceName) { + Assert.hasText(holder.getFlowDefinitionId(), "The flow holder to index must return a non-blank flow id"); + Map namespace = getNamespace(namespaceName); + namespace.put(holder.getFlowDefinitionId(), holder); + } + + /** + * Returns the identified flow definition holder. Throws an exception if it cannot be found. + */ + private FlowDefinitionHolder getFlowDefinitionHolder(String flowPath) throws NoSuchFlowDefinitionException { + Map namespace = getNamespace(FlowPathUtils.extractFlowNamespace(flowPath)); + FlowDefinitionHolder flowHolder = (FlowDefinitionHolder) namespace.get(FlowPathUtils.extractFlowId(flowPath)); + if (flowHolder == null) { + throw new NoSuchFlowDefinitionException(flowPath, getFlowDefinitionPaths()); + } + return flowHolder; + } + + /** + * Returns the namespace map for a given namespace. Creates the map if it does not exist. + */ + private Map getNamespace(String namespace) { + if (!flowDefinitions.containsKey(namespace)) { + flowDefinitions.put(namespace, new TreeMap()); + } + return (Map) flowDefinitions.get(namespace); + } + + public String toString() { + return new ToStringCreator(this).append("flowDefinitions", flowDefinitions).append("parent", parent).toString(); + } } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowDefinitionRegistryMBean.java b/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowDefinitionRegistryMBean.java index 085dda94..a6a60dda 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowDefinitionRegistryMBean.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowDefinitionRegistryMBean.java @@ -16,13 +16,12 @@ package org.springframework.webflow.definition.registry; /** - * A management interface for managing flow definition registries at runtime. - * Provides the ability to query the size and state of the registry, as well as - * refresh registered flow definitions at runtime. + * A management interface for managing flow definition registries at runtime. Provides the ability to query the size and + * state of the registry, as well as refresh registered flow definitions at runtime. *

- * Flow registries that implement this interface may be exposed for management - * over the JMX protocol. The following is an example of using Spring's JMX - * MBeanExporter to export a flow registry to an MBeanServer: + * Flow registries that implement this interface may be exposed for management over the JMX protocol. The following is + * an example of using Spring's JMX MBeanExporter to export a flow registry to an MBeanServer: + * *

  *     <!-- Creates the registry of flow definitions for this application -->
  *     <bean name="flowRegistry" class="org.springframework.webflow...XmlFlowRegistryFactoryBean">
@@ -44,18 +43,19 @@ package org.springframework.webflow.definition.registry;
  *         </property>
  *     </bean>
  * 
- * With the above configuration, you may then use any JMX client (such as Sun's - * jConsole which ships with JDK 1.5) to refresh flow definitions at runtime. + * + * With the above configuration, you may then use any JMX client (such as Sun's jConsole which ships with JDK 1.5) to + * refresh flow definitions at runtime. * * @author Keith Donald */ public interface FlowDefinitionRegistryMBean { /** - * Returns the ids of the flow definitions registered in this registry. - * @return the flow definition ids + * Returns the paths of the flow definitions registered in this registry. + * @return the flow definition paths */ - public String[] getFlowDefinitionIds(); + public String[] getFlowDefinitionPaths(); /** * Return the number of flow definitions registered in this registry. @@ -64,28 +64,23 @@ public interface FlowDefinitionRegistryMBean { public int getFlowDefinitionCount(); /** - * Queries this registry to determine if a specific flow is contained within - * it. - * @param id the flow definition id - * @return true if a flow definition is contained in this registry with the - * id provided + * Queries this registry to determine if a specific flow is contained within it. + * @param flowPath the flow definition path + * @return true if a flow definition is contained in this registry with the id provided */ - public boolean containsFlowDefinition(String id); + public boolean containsFlowDefinition(String flowPath); /** - * Refresh this flow definition registry, reloading all Flow definitions - * from their externalized representations. + * Refresh this flow definition registry, reloading all Flow definitions from their externalized representations. */ public void refresh() throws FlowDefinitionConstructionException; /** - * Refresh the Flow definition in this registry with the id - * provided, reloading it from it's externalized representation. - * @param flowDefinitionId the id of the flow definition to refresh - * @throws NoSuchFlowDefinitionException if a flow with the id provided is not - * stored in this registry + * Refresh the Flow definition in this registry with the path provided, reloading it from it's + * externalized representation. + * @param flowPath the path of the flow definition to refresh + * @throws NoSuchFlowDefinitionException if a flow with the id provided is not stored in this registry */ - public void refresh(String flowDefinitionId) - throws NoSuchFlowDefinitionException, FlowDefinitionConstructionException; + public void refresh(String flowPath) throws NoSuchFlowDefinitionException, FlowDefinitionConstructionException; } \ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowPathUtils.java b/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowPathUtils.java new file mode 100644 index 00000000..f93aa284 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/FlowPathUtils.java @@ -0,0 +1,55 @@ +package org.springframework.webflow.definition.registry; + +import org.springframework.util.Assert; + +/** + * Simple utility for working with flow paths. Only intended for internal use. + * + * @author Ben Hale + */ +class FlowPathUtils { + + private static final String PATH_DELIMITER = "/"; + + /** + * Parses a flow path and returns the namespace part of the path. + * @param flowPath The path to parse + * @return The namespace part of the path + */ + public static String extractFlowNamespace(String flowPath) { + Assert.hasText(flowPath, "The flow path must not be empty or null"); + int index = flowPath.lastIndexOf(PATH_DELIMITER); + if (index == -1) { + return ""; + } else { + return flowPath.substring(0, index); + } + } + + /** + * Parses a flow path and returns the id part of the path. + * @param flowPath The path to parse + * @return The id part of the path + */ + public static String extractFlowId(String flowPath) { + Assert.hasText(flowPath, "The flow path must not be empty or null"); + int index = flowPath.lastIndexOf(PATH_DELIMITER); + if (index == -1) { + return flowPath; + } else { + return flowPath.substring(index + 1); + } + } + + /** + * Creates a flow path based on given namespace and id. + * @param namespace The namespace of the path + * @param id The id of the path + * @return The complete flow path + */ + public static String buildFlowPath(String namespace, String id) { + Assert.notNull(namespace, "namespace must have some value"); + Assert.hasText(id, "The id must not be empty or null"); + return namespace + PATH_DELIMITER + id; + } +} diff --git a/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/StaticFlowDefinitionHolder.java b/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/StaticFlowDefinitionHolder.java index 3728ace4..d26dea95 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/StaticFlowDefinitionHolder.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/definition/registry/StaticFlowDefinitionHolder.java @@ -18,8 +18,7 @@ package org.springframework.webflow.definition.registry; import org.springframework.webflow.definition.FlowDefinition; /** - * A simple flow definition holder that just holds a constant singleton - * reference to a flow definition. + * A simple flow definition holder that just holds a constant singleton reference to a flow definition. * * @author Keith Donald */ 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 85abb5dc..3d187d05 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 @@ -27,14 +27,13 @@ import org.springframework.webflow.engine.Flow; import org.springframework.webflow.util.ResourceHolder; /** - * A flow definition holder that can detect changes on an underlying flow - * definition resource and refresh that resource automatically. + * A flow definition holder that can detect changes on an underlying flow definition resource and refresh that resource + * automatically. *

- * This class is threadsafe. + * This class is thread-safe. *

- * Note that this {@link FlowDefinition} holder uses a {@link Flow} assembler. - * This is normal since a {@link Flow} is a {@link FlowDefinition}! This class - * bridges the abstract world of {@link FlowDefinition flow definitions} + * Note that this {@link FlowDefinition} holder uses a {@link Flow} assembler. This is normal since a {@link Flow} is a + * {@link FlowDefinition}! This class bridges the abstract world of {@link FlowDefinition flow definitions} * with the concrete world of {@link Flow flow implementations}. * * @see FlowDefinition @@ -44,7 +43,7 @@ import org.springframework.webflow.util.ResourceHolder; * @author Keith Donald */ public class RefreshableFlowDefinitionHolder implements FlowDefinitionHolder { - + private static final Log logger = LogFactory.getLog(RefreshableFlowDefinitionHolder.class); /** @@ -58,21 +57,19 @@ public class RefreshableFlowDefinitionHolder implements FlowDefinitionHolder { private FlowAssembler assembler; /** - * A last modified date for the backing flow definition resource, used to support - * automatic reassembly on resource change. + * A last modified date for the backing flow definition resource, used to support automatic reassembly on resource + * change. */ private long lastModified; /** - * A flag indicating whether or not this holder is in the middle of the - * assembly process. + * A flag indicating whether or not this holder is in the middle of the assembly process. */ private boolean assembling; /** - * Creates a new refreshable flow definition holder that uses the configured - * assembler (GOF director) to drive flow assembly, on initial use and on any - * resource change or refresh. + * Creates a new refreshable flow definition holder that uses the configured assembler (GOF director) to drive flow + * assembly, on initial use and on any resource change or refresh. * @param assembler the flow assembler to use */ public RefreshableFlowDefinitionHolder(FlowAssembler assembler) { @@ -91,8 +88,7 @@ public class RefreshableFlowDefinitionHolder implements FlowDefinitionHolder { if (!isAssembled()) { lastModified = calculateLastModified(); assembleFlow(); - } - else { + } else { refreshIfChanged(); } return flowDefinition; @@ -101,7 +97,7 @@ public class RefreshableFlowDefinitionHolder implements FlowDefinitionHolder { public synchronized void refresh() throws FlowBuilderException { assembleFlow(); } - + // internal helpers /** @@ -122,8 +118,8 @@ public class RefreshableFlowDefinitionHolder implements FlowDefinitionHolder { long calculatedLastModified = calculateLastModified(); if (this.lastModified < calculatedLastModified) { if (logger.isDebugEnabled()) { - logger.debug("Resource modification detected, reloading flow definition with id '" + - assembler.getFlowId() + "'"); + logger.debug("Resource modification detected, reloading flow definition with id '" + + assembler.getFlowId() + "'"); } assembleFlow(); this.lastModified = calculatedLastModified; @@ -131,21 +127,18 @@ public class RefreshableFlowDefinitionHolder implements FlowDefinitionHolder { } /** - * Helper that retrieves the last modified date by querying the backing flow - * resource. + * Helper that retrieves the last modified date by querying the backing flow resource. * @return the last modified date, or -1 if it could not be retrieved */ protected long calculateLastModified() { if (getFlowBuilder() instanceof ResourceHolder) { - Resource resource = ((ResourceHolder)getFlowBuilder()).getResource(); + Resource resource = ((ResourceHolder) getFlowBuilder()).getResource(); if (logger.isDebugEnabled()) { - logger.debug( - "Calculating last modified timestamp for flow definition resource '" + resource + "'"); + logger.debug("Calculating last modified timestamp for flow definition resource '" + resource + "'"); } try { return resource.getFile().lastModified(); - } - catch (IOException e) { + } catch (IOException e) { // ignore, last modified checks not supported } } @@ -153,7 +146,7 @@ public class RefreshableFlowDefinitionHolder implements FlowDefinitionHolder { } /** - * Returns the last modifed date of the backed flow definition resource. + * Returns the last modified date of the backed flow definition resource. * @return the last modified date */ protected long getLastModified() { @@ -161,8 +154,7 @@ public class RefreshableFlowDefinitionHolder implements FlowDefinitionHolder { } /** - * Assemble the held flow definition, delegating to the configured - * FlowAssembler (director). + * Assemble the held flow definition, delegating to the configured FlowAssembler (director). */ protected void assembleFlow() throws FlowBuilderException { if (logger.isDebugEnabled()) { @@ -171,15 +163,13 @@ public class RefreshableFlowDefinitionHolder implements FlowDefinitionHolder { try { assembling = true; flowDefinition = assembler.assembleFlow(); - } - finally { + } finally { assembling = false; } } /** - * Returns a flag indicating if this holder has performed and completed - * flow definition assembly. + * Returns a flag indicating if this holder has performed and completed flow definition assembly. */ protected boolean isAssembled() { return flowDefinition != null; diff --git a/spring-webflow/src/test/java/org/springframework/webflow/definition/registry/FlowDefinitionRegistryImplTests.java b/spring-webflow/src/test/java/org/springframework/webflow/definition/registry/FlowDefinitionRegistryImplTests.java index 3f6227a1..20662b87 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/definition/registry/FlowDefinitionRegistryImplTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/definition/registry/FlowDefinitionRegistryImplTests.java @@ -25,27 +25,36 @@ import org.springframework.webflow.definition.StateDefinition; * Unit tests for {@link FlowDefinitionRegistryImpl}. */ public class FlowDefinitionRegistryImplTests extends TestCase { - + private FlowDefinitionRegistryImpl registry = new FlowDefinitionRegistryImpl(); private FlowDefinition fooFlow; + private FlowDefinition barFlow; + protected void setUp() { fooFlow = new FooFlow(); + barFlow = new BarFlow(); } public void testEmptyRegistryAsserts() { assertEquals(0, registry.getFlowDefinitionCount()); - assertEquals(0, registry.getFlowDefinitionIds().length); - assertEquals(0, registry.getFlowDefinitions().length); + assertEquals(0, registry.getFlowDefinitionPaths().length); } public void testNoSuchFlowDefinition() { try { registry.getFlowDefinition("bogus"); fail("Should've bombed with NoSuchFlow"); + } catch (NoSuchFlowDefinitionException e) { } - catch (NoSuchFlowDefinitionException e) { + } + + public void testNoSuchFlowDefinitionWithNamespace() { + try { + registry.getFlowDefinition("/namespace/bogus"); + fail("Should've bombed with NoSuchFlow"); + } catch (NoSuchFlowDefinitionException e) { } } @@ -53,11 +62,17 @@ public class FlowDefinitionRegistryImplTests extends TestCase { public void testRegisterFlow() { registry.registerFlowDefinition(new StaticFlowDefinitionHolder(fooFlow)); assertEquals(1, registry.getFlowDefinitionCount()); - assertEquals("foo", registry.getFlowDefinitionIds()[0]); - assertEquals("foo", registry.getFlowDefinitions()[0].getId()); + assertEquals("/foo", registry.getFlowDefinitionPaths()[0]); assertEquals("foo", registry.getFlowDefinition("foo").getId()); } + public void testRegisterFlowWithNamespace() { + registry.registerFlowDefinition(new StaticFlowDefinitionHolder(barFlow), "/namespace"); + assertEquals(1, registry.getFlowDefinitionCount()); + assertEquals("/namespace/bar", registry.getFlowDefinitionPaths()[0]); + assertEquals("bar", registry.getFlowDefinition("/namespace/bar").getId()); + } + public void testRegisterFlowSameIds() { registry.registerFlowDefinition(new StaticFlowDefinitionHolder(fooFlow)); FooFlow newFlow = new FooFlow(); @@ -66,6 +81,14 @@ public class FlowDefinitionRegistryImplTests extends TestCase { assertSame(newFlow, registry.getFlowDefinition("foo")); } + public void testRegisterFlowSameIdsWithNamespace() { + registry.registerFlowDefinition(new StaticFlowDefinitionHolder(barFlow), "/namespace"); + BarFlow newFlow = new BarFlow(); + registry.registerFlowDefinition(new StaticFlowDefinitionHolder(newFlow), "/namespace"); + assertEquals(1, registry.getFlowDefinitionCount()); + assertSame(newFlow, registry.getFlowDefinition("/namespace/bar")); + } + public void testRegisterMultipleFlows() { registry.registerFlowDefinition(new StaticFlowDefinitionHolder(fooFlow)); FooFlow newFlow = new FooFlow(); @@ -76,13 +99,30 @@ public class FlowDefinitionRegistryImplTests extends TestCase { assertSame(newFlow, registry.getFlowDefinition("bar")); } + public void testRegisterMultipleFlowsWithNamespace() { + registry.registerFlowDefinition(new StaticFlowDefinitionHolder(barFlow), "/namespace"); + BarFlow newFlow = new BarFlow(); + newFlow.id = "foo"; + registry.registerFlowDefinition(new StaticFlowDefinitionHolder(newFlow), "/namespace"); + assertEquals(2, registry.getFlowDefinitionCount()); + assertSame(barFlow, registry.getFlowDefinition("/namespace/bar")); + assertSame(newFlow, registry.getFlowDefinition("/namespace/foo")); + } + public void testRefresh() { testRegisterMultipleFlows(); registry.refresh(); assertEquals(2, registry.getFlowDefinitionCount()); assertSame(fooFlow, registry.getFlowDefinition("foo")); } - + + public void testRefreshWithNamespace() { + testRegisterMultipleFlowsWithNamespace(); + registry.refresh(); + assertEquals(2, registry.getFlowDefinitionCount()); + assertSame(barFlow, registry.getFlowDefinition("/namespace/bar")); + } + public void testRefreshValidFlow() { testRegisterMultipleFlows(); registry.refresh("foo"); @@ -90,17 +130,33 @@ public class FlowDefinitionRegistryImplTests extends TestCase { assertSame(fooFlow, registry.getFlowDefinition("foo")); } + public void testRefreshValidFlowWithNamespace() { + testRegisterMultipleFlowsWithNamespace(); + registry.refresh("/namespace/bar"); + assertEquals(2, registry.getFlowDefinitionCount()); + assertSame(barFlow, registry.getFlowDefinition("/namespace/bar")); + } + public void testRefreshNoSuchFlow() { testRegisterMultipleFlows(); try { registry.refresh("bogus"); fail("Should've bombed with NoSuchFlow"); - } - catch (NoSuchFlowDefinitionException e) { + } catch (NoSuchFlowDefinitionException e) { } } - + + public void testRefreshNoSuchFlowWithNamespace() { + testRegisterMultipleFlowsWithNamespace(); + try { + registry.refresh("/namespace/bogus"); + fail("Should've bombed with NoSuchFlow"); + } catch (NoSuchFlowDefinitionException e) { + + } + } + public void testParentHierarchy() { testRegisterMultipleFlows(); FlowDefinitionRegistryImpl child = new FlowDefinitionRegistryImpl(); @@ -111,9 +167,47 @@ public class FlowDefinitionRegistryImplTests extends TestCase { assertEquals("bar", child.getFlowDefinition("bar").getId()); } + public void testParentHierarchyWithNamespace() { + testRegisterMultipleFlowsWithNamespace(); + FlowDefinitionRegistryImpl child = new FlowDefinitionRegistryImpl(); + child.setParent(registry); + BarFlow barFlow = new BarFlow(); + child.registerFlowDefinition(new StaticFlowDefinitionHolder(barFlow), "/namespace"); + assertSame(barFlow, child.getFlowDefinition("/namespace/bar")); + assertEquals("bar", child.getFlowDefinition("/namespace/bar").getId()); + } + private static class FooFlow implements FlowDefinition { private String id = "foo"; - + + public AttributeMap getAttributes() { + return null; + } + + public String getCaption() { + return null; + } + + public String getDescription() { + return null; + } + + public String getId() { + return id; + } + + public StateDefinition getStartState() { + return null; + } + + public StateDefinition getState(String id) throws IllegalArgumentException { + return null; + } + } + + private static class BarFlow implements FlowDefinition { + private String id = "bar"; + public AttributeMap getAttributes() { return null; } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/definition/registry/FlowPathUtilsTests.java b/spring-webflow/src/test/java/org/springframework/webflow/definition/registry/FlowPathUtilsTests.java new file mode 100644 index 00000000..6eb24759 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/definition/registry/FlowPathUtilsTests.java @@ -0,0 +1,130 @@ +package org.springframework.webflow.definition.registry; + +import junit.framework.TestCase; + +public class FlowPathUtilsTests extends TestCase { + + public void testNamespaceWithNoSlash() { + assertEquals("Incorrect namespace", "", FlowPathUtils.extractFlowNamespace("flow")); + } + + public void testNamespaceWithSlash() { + assertEquals("Incorrect namespace", "", FlowPathUtils.extractFlowNamespace("/flow")); + } + + public void testNamespaceWithNamespace() { + assertEquals("Incorrect namespace", "/namespace", FlowPathUtils.extractFlowNamespace("/namespace/flow")); + } + + public void teswtNamespaceWithComplexNamespace() { + assertEquals("Incorrect namespace", "/complex/namespace", FlowPathUtils.extractFlowNamespace("/complex/namespace/flow")); + } + + public void testNamespaceEmpty() { + try { + FlowPathUtils.extractFlowNamespace(""); + fail("Should have detected empty input"); + } catch (IllegalArgumentException e) { + } + } + + public void testNamespaceWhitespace() { + try { + FlowPathUtils.extractFlowNamespace(" "); + fail("Should have detected empty input"); + } catch (IllegalArgumentException e) { + } + } + + public void testNamespaceNull() { + try { + FlowPathUtils.extractFlowNamespace(null); + fail("Should have detected empty input"); + } catch (IllegalArgumentException e) { + } + } + + public void testIdWithNoSlash() { + assertEquals("Incorrect id", "flow", FlowPathUtils.extractFlowId("flow")); + } + + public void testIdWithSlash() { + assertEquals("Incorrect id", "flow", FlowPathUtils.extractFlowId("/flow")); + } + + public void testIdWithNamespace() { + assertEquals("Incorrect id", "flow", FlowPathUtils.extractFlowId("/namespace/flow")); + } + + public void testIdWithComplexNamespace() { + assertEquals("Incorrect id", "flow", FlowPathUtils.extractFlowId("/complex/namespace/flow")); + } + + public void testIdEmpty() { + try { + FlowPathUtils.extractFlowId(""); + fail("Should have detected empty input"); + } catch (IllegalArgumentException e) { + } + } + + public void testIdWhitespace() { + try { + FlowPathUtils.extractFlowId(" "); + fail("Should have detected empty input"); + } catch (IllegalArgumentException e) { + } + } + + public void testIdNull() { + try { + FlowPathUtils.extractFlowId(null); + fail("Should have detected empty input"); + } catch (IllegalArgumentException e) { + } + } + + public void testPathWithEmptyNamespace() { + assertEquals("Incorrect path", "/flow", FlowPathUtils.buildFlowPath("", "flow")); + } + + public void testPathWithNamespace() { + assertEquals("Incorrect path", "/namespace/flow", FlowPathUtils.buildFlowPath("/namespace", "flow")); + } + + public void testPathWithComplexNamespace() { + assertEquals("Incorrect path", "/complex/namespace/flow", FlowPathUtils.buildFlowPath("/complex/namespace", "flow")); + } + + public void testPathWithNullNamespace() { + try { + FlowPathUtils.buildFlowPath(null, "flow"); + fail("Should have detected empty input"); + } catch (IllegalArgumentException e) { + } + } + + public void testPathWithEmptyId() { + try { + FlowPathUtils.buildFlowPath("", ""); + fail("Should have detected empty input"); + } catch (IllegalArgumentException e) { + } + } + + public void testPathWithWhitespaceId() { + try { + FlowPathUtils.buildFlowPath("", " "); + fail("Should have detected empty input"); + } catch (IllegalArgumentException e) { + } + } + + public void testPathWithNullId() { + try { + FlowPathUtils.buildFlowPath("", null); + fail("Should have detected empty input"); + } catch (IllegalArgumentException e) { + } + } +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/XmlFlowRegistrarTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/XmlFlowRegistrarTests.java index 16a89502..2b54b550 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/XmlFlowRegistrarTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/XmlFlowRegistrarTests.java @@ -38,7 +38,7 @@ public class XmlFlowRegistrarTests extends TestCase { registrar.addLocation(new ClassPathResource("flow.xml", getClass())); registrar.registerFlowDefinitions(registry); assertEquals(1, registry.getFlowDefinitionCount()); - assertEquals("flow", registry.getFlowDefinitions()[0].getId()); + assertEquals("flow", registry.getFlowDefinition("flow").getId()); } public void testAddResource() { @@ -46,6 +46,6 @@ public class XmlFlowRegistrarTests extends TestCase { registrar.addResource(new FlowDefinitionResource("foo", new ClassPathResource("flow.xml", getClass()))); registrar.registerFlowDefinitions(registry); assertEquals(1, registry.getFlowDefinitionCount()); - assertEquals("foo", registry.getFlowDefinitions()[0].getId()); + assertEquals("foo", registry.getFlowDefinition("foo").getId()); } } \ No newline at end of file diff --git a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/XmlFlowRegistryFactoryBeanTests.java b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/XmlFlowRegistryFactoryBeanTests.java index cf402e37..c616c130 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/XmlFlowRegistryFactoryBeanTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/engine/builder/xml/XmlFlowRegistryFactoryBeanTests.java @@ -31,9 +31,9 @@ public class XmlFlowRegistryFactoryBeanTests extends TestCase { factoryBean.setFlowLocations(locations); factoryBean.setBeanFactory(new StaticListableBeanFactory()); factoryBean.afterPropertiesSet(); - FlowDefinitionRegistry registry = (FlowDefinitionRegistry)factoryBean.getObject(); + FlowDefinitionRegistry registry = (FlowDefinitionRegistry) factoryBean.getObject(); assertEquals(1, registry.getFlowDefinitionCount()); - assertEquals("flow", registry.getFlowDefinitions()[0].getId()); + assertEquals("flow", registry.getFlowDefinition("flow").getId()); } public void testCreateFromDefinitions() throws Exception { @@ -42,8 +42,8 @@ public class XmlFlowRegistryFactoryBeanTests extends TestCase { factoryBean.setFlowDefinitions(properties); factoryBean.setBeanFactory(new StaticListableBeanFactory()); factoryBean.afterPropertiesSet(); - FlowDefinitionRegistry registry = (FlowDefinitionRegistry)factoryBean.getObject(); + FlowDefinitionRegistry registry = (FlowDefinitionRegistry) factoryBean.getObject(); assertEquals(1, registry.getFlowDefinitionCount()); - assertEquals("foo", registry.getFlowDefinitions()[0].getId()); + assertEquals("foo", registry.getFlowDefinition("foo").getId()); } }