Announcement Announcement Module
Collapse
No announcement yet.
Spring Web Flow: Subflow in a different controller Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Spring Web Flow: Subflow in a different controller

    I have a Spring Web Flow problem that occurs when I attempt to execute a subflow in a controller different than the calling flow. The standard flow(id, builder) method in the AbstractFlowBuilder uses the service locator to resolve the subflow from the Spring bean context. Unfortunately, my subflow bean is in a separate context used within a different controller. Should I be creating a new Flow instance directly in the SubFlowState constructor rather than using the flow() method? That would result in multiple instances of the same flow in memory at the same time. Not sure if that is a good thing...

    The reason behind this craziness is that Gaijin Studio (Eclipse SWF plugin) generates all of the items required for each flow separately. Each flow has a separate controller and supporting contexts, logically separating the beans/config associated with it from the others. The only thing shared is the root context.

    Any ideas on how to make this work?

    Thanks,
    Derek Adams
    [email protected]

  • #2
    Hmm. I guess having one context per flow isn't a bad idea in principle, as flows are intended to be self-contained. Although, if you do this, you're likely going to end up with a lot of tiny spring config files for simple flows, and you might end up duplicating action deployment for flows that share actions (in addition to the subflow resolution problem).

    I'm thinking it might be better to group a set of related flows and associated artificats together as a single deployment unit, though, rather than having 1 flow per context.

    There is also no requirement for a separate controller per flow. A single flow controller can serve all flows, parameterized by the views. This further reduces configuration needs.

    With that said, it seems one possible solution here is to have a FlowLocator implementation go out and access all flow contexts, searching for the requested flow artifact. A master FlowController would need to do this, as would any Flow needing to spawn a subflow. (Though I'm not sure if I like this, as you really can't ensure that a given flow or action id is the 'right' one - I think I like "one context per flow group, where each context has a flow controller for managing one or more related flows and exists independently of other "flow groups")

    You could also instantiate the flow directly I guess, but as you mentioned that duplicates objects and also increases configuration needs (particularly if you need a custom FlowCreator strategy.)

    Comment


    • #3
      One of the things I was aiming to do with Gaijin was create all of the supporting Spring configuration files automatically from Eclipse. Right now it generates the controller config file for you (flow controller bean, flow builder bean, and unique per-flow message source). It also creates a per-flow supporting config file where you add your actions, viewResolver, etc. Both of the config files are automatically mapped to the dispatcher servlet for the controller in the web.xml. Here is the web.xml Gaijin generates for the project I am testing with ..

      Code:
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
                               "http&#58;//java.sun.com/dtd/web-app_2_3.dtd">
      <web-app>
          <context-param>
              <param-name>contextConfigLocation</param-name>
              <param-value>WEB-INF/applicationContext.xml</param-value>
          </context-param>
          <listener>
              <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
          </listener>
          <servlet>
              <servlet-name>phoneNumberList</servlet-name>
              <display-name>Phone Number Listing Process</display-name>
              <description>This flow allows a user to search for phone numbers and produces a list.</description>
              <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
              <init-param>
                  <param-name>contextConfigLocation</param-name>
                  <param-value>WEB-INF/contexts/PhoneNumberList-generated.xml, WEB-INF/contexts/PhoneNumberList-support.xml</param-value>
              </init-param>
          </servlet>
          <servlet>
              <servlet-name>user_createUser</servlet-name>
              <display-name>Create User</display-name>
              <description>Create a new user.</description>
              <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
              <init-param>
                  <param-name>contextConfigLocation</param-name>
                  <param-value>WEB-INF/contexts/user/CreateUser-generated.xml, WEB-INF/contexts/user/CreateUser-support.xml</param-value>
              </init-param>
          </servlet>
          <servlet-mapping>
              <servlet-name>phoneNumberList</servlet-name>
              <url-pattern>/phoneNumberList.do</url-pattern>
          </servlet-mapping>
          <servlet-mapping>
              <servlet-name>user_createUser</servlet-name>
              <url-pattern>/user/createUser.do</url-pattern>
          </servlet-mapping>
      </web-app>
      ... and the supporting PhoneNumberList-generated.xml for the main web flow...

      Code:
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
                             "http&#58;//www.springframework.org/dtd/spring-beans.dtd">
      <beans>
          <bean class="org.springframework.context.support.ReloadableResourceBundleMessageSource" id="messageSource">
              <property name="cacheSeconds">
                  <value>10</value>
              </property>
              <property name="basenames">
                  <list>
                      <value>WEB-INF/resources/PhoneNumberList</value>
                  </list>
              </property>
          </bean>
          <bean class="org.springframework.web.flow.mvc.FlowController" id="phoneNumberListController" name="/phoneNumberList.do">
              <property name="cacheSeconds">
                  <value>0</value>
              </property>
              <property name="flow">
                  <ref bean="phoneNumberList"/>
              </property>
          </bean>
          <bean class="org.springframework.web.flow.config.FlowFactoryBean" id="phoneNumberList">
              <property name="flowBuilder">
                  <bean class="gen.PhoneNumberListBuilder"/>
              </property>
          </bean>
      </beans>
      I'll take a look at combining the flow declarations into a central bean context file to get around the bean referencing problem. Maybe it makes sense to add them as an extra context config file at the root context level so that all controllers will have access to them automatically.

      Thanks for your quick response!
      Derek
      [email protected]

      Comment


      • #4
        I did a quick test and the approach above seems to work. All of the flow factory beans are declared in a separate file and loaded at the root context level. Using that approach, you can use a single controller or any number of controllers and still access subflows.

        The main reason I have been using multiple flow dispatchers/controllers is that I want to do things like have a unique message source per flow. Also applying interceptors on a per-flow basis would make it easy to add flow-level security constraints and other fun stuff. Honestly, there are probably better solutions for these problems, but separating the flows has been the easiest way I have found so far.

        If all goes well, I should be able to get the changes in and released soon so that you can take a look.

        Thanks!
        Derek

        Comment


        • #5
          The only issue I have with that approach is now all your flow artifacts, including the flows themseves as well as their associated actions and attribute mappers, are going to share the same context as the root context, where you have your logical middle tier. That's probably not that big of deal in practice, but it could result in a rather large context for complex applications (and makes it possible for beans in the web layer to be referenced by beans in the service layer.)

          So I guess what I'm saying is I like the hierarchy there - where the flow definitions are at a layer up from the service layer. I like the notion of a "flow unit" (or group) that supports a use case; where each group could then have its own dispatcher/controller/message source, in addition to all flow artifacts needed. I'd just make it where you could have as many flows as you want in that group -- so perhaps just one group for a small app, all routed through a single controller -- or perhaps multiple groups for a large app, all independent in the fact they have a different dispatcher+web context, controller, and flow artifacts.

          Just throwing ideas out there.

          Comment


          • #6
            Good points! I didn't think about the fact that all of the supporting actions and other artifacts would have to be available in the root context too. That would get messy fast.

            I already support the idea of flow groups in Gaijin, so I will change things up to have one dispatcher/controller/messagesource/support-context per group. The only drawback I see right now is that you will not be able to access flows from other groups via a subflow call, which wouldn't make sense anyway. The new structure should still be really flexible and much easier config-wise.

            Thanks for your help!
            Derek

            Comment


            • #7
              The only drawback I see right now is that you will not be able to access flows from other groups via a subflow call, which wouldn't make sense anyway.
              If somebody really wanted to do something like that, it could still be done using a custom FlowLocator.

              Also, note that we had to (because of refactoring and the introduction of the flow storage strategies) change the flow definition syntax a bit in the upcoming SWF preview 2 release. Here is an example:
              Code:
              	<bean id="frontController" name="/upload.htm" class="org.springframework.web.flow.mvc.FlowController">
              		<property name="flowExecutionManager">
              			<bean class="org.springframework.web.flow.execution.servlet.HttpServletFlowExecutionManager">
              				<property name="flow" ref="upload"/>
              			</bean>
              		</property>
              	</bean>
              	
              	<bean id="upload" class="org.springframework.web.flow.config.FlowFactoryBean">
              		<property name="flowBuilder">
              			<bean class="org.springframework.web.flow.config.XmlFlowBuilder">
              				<property name="resource" value="/WEB-INF/upload-flow.xml"/>
              			</bean>
              		</property>
              	</bean>
              Note how the flow execution manager has become a first class object. If you're letting the view tell the controller what flow to use, the definition is just:

              Code:
              <bean id="phonebookFrontController" name="/phonebook.htm" class="org.springframework.web.flow.mvc.FlowController"/>
              We're planning to release SWF preview 2 in a couple of days so it's probably best if you encorporate this into the first SWF enabled release of Gaijin studio.

              Erwin

              Comment


              • #8
                The changes for PR2 sound pretty cool! I was trying to get the latest from CVS to check out the samples and got an error:
                Problems reported while synchronizing CVS Workspace. 0 of 1 resources were synchronized.
                An error occurred synchronizing /spring: The server reported an error: No space left on device
                I am guessing this is a disk space problem on the SF server, but I figured I would let you know.

                Derek

                Comment


                • #9
                  Looks like the CVS problem is fixed now.

                  Comment

                  Working...
                  X