Interoperating With Struts and Page Flows

Page flows in WebLogic Workshop 8.1 are based on Struts 1.1, which is a part of the Jakarta™ Project by the Apache Software Foundation™. The programming framework that is provided by WebLogic Workshop page flows adds many enhancements, including the automatic generation and synchronization of XML configuration files, plus a sophisticated graphical IDE to define, build, deploy, and maintain the web application.

WebLogic Workshop provides interchangeable support for Struts modules and page flow controller classes working together in the same web project. This feature, called "Struts Interop," extends the reach of both types of applications:

The files that comprise your Struts modules and the page flows, if they are to interoperate, must exist in the same web project. Unless there are conflicts with the names of page flow directories, you should be able to use your existing hierarchy of directories for the files that are used by the Struts module. However, the class or JAR files must reside in specific directories, as noted in Requirements of the Struts Modules and Page Flows.

The Struts Interop feature is different from the Struts Merge feature described in Merging Struts Artifacts Into Page Flows. In the case of the Struts Merge feature, you place a @jpf:controller struts-merge="..." annotation in your page flow JPF file. This step enables an existing, all-Struts XML configuration file to be merged (at project compilation time) with the page flow's generated jpf-struts-config-<pageflow>.xml file. The purpose of the Struts Merge feature is to enable you to override page flow defaults, or to specify settings for the page flow that are not provided by page flow annotations or their attributes. The Struts merge files should typically be small and only modify the page flow and its actions and form beans. While you could, for example, add action mappings in the Struts merge file, BEA does not recommend this practice. Struts action mappings should be declared in the page flow's .jpf file with @jpf annotations, and then (if necessary) modified with the Struts merge file.

In the case of the "Struts Interop" feature, the support allows your legacy Struts modules and your new page flows to exist in the same web project, and allows them to interact by using form beans.

You can, of course, use both the Struts Merge and Struts Interop features in a page flow. For detailed information, see A Struts Interop and Struts Merge Example in this topic.

Requirements of the Struts Modules and Page Flows

Here are the requirements of Struts modules and page flows that will interoperate with each other in the same web project:

A Struts Interop and Struts Merge Example

The following example combines Struts Merge and Struts Interop features to demonstrate the interoperability between page flows and Struts. The strutsInteropController page flow instantiates a form bean, JpfFormBean, then passes this form to a Struts module named strutsModule. The Struts module then alters the contents of the form and passes it back to the page flow. Because page flow forms derive from Struts forms, this sharing of the form bean is possible.

By default, page flows scope form bean instances to the request. But because in this example we want to use session-scoping to share the form bean between the page flow and the struts module, strutsModule, we will cause the page flow to operate on a session-scoped form bean. We do this by using the struts-merge attribute on the @jpf:controller annotation in the strutsInteropController.jpf source file. This annotation will cause the contents of the specified Struts XML file to be merged-in with the generated jpf-struts-config-strutsInterop.xml file.

The form bean we use in this example has a single field, "field1". At each step, the action (whether page flow or Struts) will update the value of "field1" so you can see that the same instance of the form is being passed around.

To demonstrate the "Struts Merge" and "Struts Interop" features in one example, we created the following entities. All these files are part of the sample application that is installed to the following location:

<WEBLOGIC_HOME>/samples/workshop/SamplesApp/WebApp/...

The file specifications shown in the following list are relative to the web project's root directory.

Next we compiled the web project that contains these entities, by selecting the WebApp project name, executing a right-mouse click, and choosing Build Project. Then we started the server and, in a browser, accessed this URL:

http://localhost:7001/WebApp/strutsInterop/strutsInteropController.jpf

Walking Through the Example

Now let's walk through the processing. It should be helpful to start with an accounting of the field1 values in the shared form. The following table shows the values, the order in which each value was set, which entity set each value, in which rendered JSP page the value is seen, and the page flow or Struts context.

Field1 String Value Set By... Rendered in...
"Form bean Field1 default value set by the form bean itself." The shared form bean, /strutsInterop/JpfFormBean.java

/strutsInterop/Jsp1.jsp

(A page flow JSP)

"Form bean Field1 value set by the page flow controller class."

The page flow controller class, /strutsInterop/strutsInteropController.jpf

/strutsModule/Jsp2.jsp

(A Struts JSP)

"Form bean Field1 value set by the Struts2 class." The Struts2 action class, /WEB-INF/src/strutsModule/Struts2.java

/strutsInterop/Jsp3.jsp

(A page flow JSP)

The following diagram illustrates the processing flow. In this diagram, the shaded boxes represent entities that are part of the page flow. The unshaded boxes represent Struts entities.

Here are the steps:

  1. When the web project was compiled, among the processing that occurred was the merger of the session-scoped jpfAction1, jpfAction2, and jpfAction3 form beans from the Struts /strutsInterop/merge-jpf-struts-config.xml file into the page flow's generated /WEB-INF/jpf-struts-config-strutsInterop.xml file, which includes:
  2.   <action-mappings>
        <action validate="false" type="strutsInterop.strutsInteropController" name="jpfFormBean" path="/jpfAction1" scope="session">
          <forward contextRelative="true" path="/strutsModule/strutsAction1.do" name="gotoStruts"/>
        </action>
        <action validate="false" type="strutsInterop.strutsInteropController" name="jpfFormBean" path="/jpfAction2" scope="session">
          <forward path="/Jsp3.jsp" name="gotoPg3"/>
        </action>
        <action validate="false" type="strutsInterop.strutsInteropController" name="jpfFormBean" path="/jpfAction3" scope="session">
          <forward contextRelative="true" path="/strutsInterop/done.jsp" name="gotoDone"/>
        </action>

    These actions are now available for use in the page flow.

  3. With the server running, a browser user accesses the strutsInteropController.jpf page flow by specifying it in the URL. For test purposes, the URL might be:
  4. http://localhost:7001/WebApp/strutsInterop/strutsInteropController.jpf

  5. When the page flow is accessed, its begin() action is run and the forward loads the Jsp1.jsp page. Also the page flow instantiates an imported form bean, JpfFormBean.
  6. This extended step will focus on what happens as the Jsp1.jsp page is rendered by the server. The pre-rendered JSP includes the following:
  7.  <netui:form action="jpfAction1">
         <netui:label value="{actionForm.field1}"/>
     </netui:form>
    
     <br/>
     <netui:anchor action="jpfAction1">Continue</netui:anchor>

    In the page flow, jpfAction1 is defined as follows:

        /**
         * @jpf:action
         * @jpf:forward name="gotoStruts" path="/strutsModule/strutsAction1.do"
         */
         protected Forward jpfAction1(JpfFormBean inForm)
         {
             inForm.setField1(this.FORM_VALUE);
             return new Forward("gotoStruts");
         } 

    In the form bean, the initial value for the actionForm context of field1 is set to a String, as follows:

    public class JpfFormBean extends com.bea.wlw.netui.pageflow.FormData
        {
        public final static String FORM_VALUE   = "Form bean Field1 default value set by the form bean itself.";
        private             String field1       = this.FORM_VALUE;

    Thus the initial form content on the rendered Jsp1.jsp (part of the page flow) displays the String. The following sample screens show only the top and bottom portions of the rendered Jsp1.jsp page:

    ...

  8. When the user clicks the Continue link on Jsp1.jsp, the jpfAction1 results in a forward to the /strutsModule/strutsAction1.do. This results in forwarding to the Struts Jsp2.jsp page and passing in content on the shared form bean. However a number of intermediate steps happen and need to be understood.
  9. Remember that Jsp1.jsp is managed by the page flow context. The first entity to set a value for field1 in the form was the form bean itself, as noted earlier. Again, the jpfAction1 method was defined as follows, and included the bolded line shown here:

        /**
         * @jpf:action
         * @jpf:forward name="gotoStruts" path="/strutsModule/strutsAction1.do"
         */
         protected Forward jpfAction1(JpfFormBean inForm)
         {
             inForm.setField1(this.FORM_VALUE);
             return new Forward("gotoStruts");
         } 

    Thus as navigation control passes from /strutsInterop/Jsp1.jsp to /strutsModule/Jsp2.jsp, the value of field1 that was set in the page flow controller class is passed, too. This value was set in the page flow with the following statement:

     public static final String FORM_VALUE = "Form bean Field1 value set by the page flow controller class.";
    That string is displayed on Jsp2.jsp when it is rendered by the server. The pre-rendered JSP includes the following:
    <%@taglib uri="http://jakarta.apache.org/struts/tags-html" prefix="html" %>
    <%@ page import="org.apache.struts.action.ActionForm" %>
    <%@ page import="strutsInterop.JpfFormBean" %>
    ...
         <%
           JpfFormBean tmpForm = (JpfFormBean) request.getSession().getAttribute("jpfFormBean");
           out.write(tmpForm.getField1());
         %>
    ...
         <html:link action="strutsAction2">Continue</html:link>

    Because the scope for the jpfAction1 form bean in the page flow was set to session, the scriplet you see in the Struts module's Jsp2.jsp can get the session context value for field1 from the shared form bean. That is why we see the following red text in the rendered Jsp2.jsp page:

    But how did we go from Jsp1.jsp in the page flow, to Jsp2.jsp in the Struts module? Let's focus now on how that happened. Remember that the page flow jpfAction1 method was annotated with this @jpf:forward:

        /**
         * @jpf:action
         * @jpf:forward name="gotoStruts" path="/strutsModule/strutsAction1.do"
         */
         protected Forward jpfAction1(JpfFormBean inForm)
         {
         inForm.setField1(this.FORM_VALUE);
         return new Forward("gotoStruts");
         } 

    The web project is running with registered servlets. One of the servlets that we registered in the /WEB-INF/web.xml is:

    <!-- Declare struts module: strutsModule -->
    <init-param>
       <param-name>config/strutsModule</param-name>
       <param-value>/WEB-INF/struts-config-strutsModule.xml</param-value>
    </init-param> 

    In the Struts module, the strutsAction1 action is defined as follows:

     <action
         path="/strutsAction1"
         name="jpfFormBean"
         scope="session"
         validate="false"
         type="strutsModule.Struts1" >
         
         <!-- The forward below forwards to a JSP that is part of the Struts module -->
         <forward name="gotoJsp2" contextRelative="true" path="/strutsModule/Jsp2.jsp" />
     </action>

    Notice how the Struts module's action mapping for /strutsAction1 contains the following line:

         type="strutsModule.Struts1" >

    That line identifies a Struts action class. In our example, the source is located in /WEB-INF/src/strutsModule/Struts1.java. When this action class runs, it returns a "gotoJsp2" forward name. Back in the Struts module, that forward name corresponds to:

     <forward name="gotoJsp2" contextRelative="true" path="/strutsModule/Jsp2.jsp"      />

    So effectively, when users clicks the Continue link on the rendered Jsp1.jsp, they are forwarded to Jsp2.jsp.

    Here is a summary about this important step. When the page flow jpfAction1 forwards to (path="/strutsModule/strutsAction1.do") the page flow runtime system gets this request and scans its list of Struts modules, looking for a Struts module called "strutsModule". As noted earlier, page flows are implemented as Struts modules. The page flow runtime finds the Struts module "strutsModule" because we declared it in the project's /WEB-INF/web.xml file. The page flow then bootstraps, or loads, the Struts module by processing the "struts-config-strutsModule.xml" file, which was also specified in the web.xml file. The runtime now has information about all the action mappings, forms, and so on, for this Struts module. It has the Struts module context. Next, the page flow runtime instantiates the Java class specified by the (type="strutsModule.Struts1") attribute in the /WEB-INF/struts-config-strutsModule.xml (the context) file and calls its "execute" method, passing in the form bean instance that was located in the session.

  10. On the rendered Jsp2.jsp page, when the user clicks the Continue link, the action named strutsAction2 is raised in the Struts module. It is defined in the registered Struts module in /WEB-INF/struts-config-strutsModule.xml as follows:
  11.  <action
         path="/strutsAction2"
         name="jpfFormBean"
         scope="session"
         validate="false"
         type="strutsModule.Struts2" >
         
         <!-- The forward below returns to the *PAGE FLOW* -->
         <forward name="gotoJpf3" contextRelative="true" path="/strutsInterop/jpfAction2.do" />
     </action>

    We are operating in the Struts module context. In /WEB-INF/src/strutsModule/Struts2.java, a new value for FORM_VALUE (field1) is set:

     public final static String FORM_VALUE = "Form bean Field1 value set by the Struts2 class.";
    

    With that new value set and the user's click of the Continue link, the strutsAction2 action forwards to the jpfAction2.do action that is defined in the page flow. When we run that action in the page flow, it results in a forward to the page flow's Jsp3.jsp, as shown in this extract from the page flow controller class:

        /**
         * This action method was raised by an action in the struts module: strutsModule.
         * 
         * @jpf:action
         * @jpf:forward name="gotoPg3" path="Jsp3.jsp"
         */
         protected Forward jpfAction2(JpfFormBean inForm)
         {
         return new Forward("gotoPg3");
         }

    Note: Struts2 forwards to (path="/strutsInterop/jpfAction2.do"). The runtime looks for a Struts module "strutsInterop" and finds the page flow "strutsInterop". Again, page flows are implemented as Struts modules. However, page flows that you create do not have to be declared in the project's /WEB-INF/web.xml. The runtime automatically registers Struts modules that are generated from page flows.

    Notice how the rendered Jsp3.jsp, which is part of the page flow, displays the field1 form value that was set by the Struts2 action class:

    The remaining processing is simply the JpfAction3 action that is raised by the Continue link on Jsp3.jsp. It forwards to the page flow's done.jsp page.

Related Topics

For information about merging the configuration XML, see:

Merging Struts Artifacts Into Page Flows

Sample Code

Struts Interoperation Sample