# 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:

- In a page flow, you can import and use form beans that are defined by Struts 1.1 modules. This allows you to take advantage of existing Struts code quickly, without having to make modifications in your Struts action classes and form beans.
- In your Struts 1.1 action classes, you can import and use form beans that are defined by page flows.

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:

- As noted earlier, you must be using Struts 1.1.
- The files that comprise the Struts module must reside in the same web project as the page flow. By convention the Struts module should reside in the web project's WEB-INF directory. The source files for the Struts action classes and form beans may reside in <project-root>/WEB-INF/src/<my-struts-directory>/. However, this location is not a requirement. You can continue to use your existing Struts directory structure in the web project, as long as you do not have naming conflicts (see next item) and both the Struts files and the page flow files are in the same web project.
- All your compiled Struts-related classes (*.class bytecode) that are in a JAR file must reside in the WEB-INF/lib directory. Your Struts *.class files, if they are not in a JAR, must go in the WEB-INF/classes directory. Placing your user-defined Struts classes or JARs in another directory, such as APP-INF/lib or <WEBLOGIC_HOME>/server/lib, will cause classloader problems.
- To avoid naming conflicts, the Struts module names and the page flow directory names must be unique across the web project. For example, if you have a page flow that is in a directory "/pageFlowOne", you cannot specify a Struts module of the same name in the web project's /WEB-INF/web.xml file. The following Struts module elements in web.xml would conflict with the /pageFlowOne directory name in the same web project.

    <init-param>
       <param-name>config/pageFlowOne</param-name>
       <param-value>/someDir/struts-config-pageFlowOne.xml</param-value>
    </init-param>

The prior example shows an incorrect setup that would result in a naming conflict. All your page flow actions will not be found.

- Remember that any JSP pages that reside in a page flow directory always render in the page flow JPF context. If you have JSP pages that raise actions, and you only want them to render in the context of the Struts module, keep those JSP files in another directory, such as in <project-root>/strutsModule.
- If your page flow controller class and the Struts action classes will share a form bean, the form bean can be defined as either an inner class or as an external Java file. Import the form bean class in the page flow JPF and the Struts action classes that will use it. For example, if the PayrollForm form bean is defined in /WEB-INF/src/payroll/PayrollForm.java:

    import payroll.PayrollForm;

You can use page flow inner-class form beans from Struts modules. The <form-bean...> element would be defined as it is in the generated jpf-struts-config-<pageflow-name>.xml file. For example:

     <form-bean type="foo.fooController$MyForm" name="myForm"/>

- When you share a form bean, in the Struts module's XML, the value of the <form-bean name=...> attribute must match exactly the value of the name attribute generated in the page flows jpf-struts-config-<pageflow-name>.xml file. The generated name usually is the name of the form bean class, minus the package name, with a lowercase first letter. For example, form bean class xyzProject.sales.OrderForm would be named "orderForm". However if there is a naming conflict when the page flow compiler goes to generate this name, another name will be used. In this case you must look at the generated jpf-struts-config-<pageflow-name>.xml file to determine the name.
- By default, page flows scope form bean instances to the request. If you are sharing a form bean, and you are passing data from (for example) a page flow, to a Struts module, and then back to the page flow, the easiest way to share the data is to use session-scoped form beans. You can accomplish this by using the Struts Merge feature to merge in session-scoped form beans with the Struts configuration XML that will be generated for your page flow. For an example, see the next section.
- If your web project combines page flows and Struts modules, you must register each Struts module by editing the project's /WEB-INF/web.xml file. For an example, see the next section.

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

- A page flow named strutsInterop:

/strutsInterop/StrutsInteropController.jpf

When you examine this file's source, notice how we are importing the externally defined JpfFormBean form bean. Also notice how we used a class-level annotation, @jpf:controller struts-merge="merge-jpf-struts-config.xml", to merge in session-scoped form beans with the Struts configuration XML that will be generated for your page flow. As a result of the merger, JpfAction1, JpfAction2, and JpfAction3 are available for use in the page flow. Note that by default, page flow form bean instances are scoped for the request. Because our processing will include an entity beyond the page flow's boundary, the easiest way to share it will be to scope it to the session, as we move from (1) the page flow, (2) to the Struts module, and then (3) back to the page flow.

- A form bean that is defined externally in the page flow directory, and is shared by the page flow and the Struts module:

/strutsInterop/JpfFormBean.java

It will be instantiated by the page flow and passed to Struts module, strutsModule, then passed back to the page flow. At each step the value of Field1 will be changed to demonstrate that the same instance of the form is being passed between page flow and Struts.

- Two Struts action classes:

/WEB-INF/src/strutsModule/Struts1.java
/WEB-INF/src/strutsModule/Struts2.java

When you examine the Struts1.java source, notice that we import the JpfFormBean that will be shared by the page flow and the Struts module. Struts1 is the first action in the Struts module and effectively causes the page flow runtime system to "bootstrap" or load the Struts module. Then the Struts1 action simply forwards to the Jsp2.jsp page, which then renders in the context of the Struts module, strutsModule.

In the source for Struts2.java, notice that we import the JpfFormBean that will be shared by the page flow and the Struts module. The Struts action method sets a new value for Field1, and then forwards back to the page flow.

- A Struts module that registers the shared JpfFormBean.java and the two Struts*.java action classes:

/WEB-INF/struts-config-strutsModule.xml

- Several JSP pages in different directories, as follows:

In the strutsInterop page flow:
/strutsInterop/Jsp1.jsp
/strutsInterop/Jsp3.jsp
/strutsInterop/done.jsp

For the Struts module, we created a JSP page that is not part of the page flow, but interoperates with it:

/strutsModule/Jsp2.jsp

- We also created a Struts merge XML file that modifies three of the /strutsInterop page flow's actions, (JpfAction1, JpfAction2, JpfAction3) and sets the form bean instance to be scoped to "session:"

/strutsInterop/merge-jpf-struts-config.xml

The purpose of merging the Struts XML is to set the scope of the form beans to session, instead of the default request. It is worth repeating the following explanation. By default, page flows scope form bean instances to the request. Because in this example we want to share the form bean between the page flow and the Struts module strutsModule, we will scope the form bean to the session. We do this by using the struts-merge attribute on the @jpf:controller annotation in the strutsInteropController.jpf source file. This class-level annotation will cause the contents of the following XML file to be merged in with the generated jpf-struts-config-strutsInterop.xml file.

- We edited the web project's /WEB-INF/web.xml file to add an entry for the Struts module:

     <init-param>
         <param-name>config/strutsModule</param-name>
         <param-value>/WEB-INF/struts-config-strutsModule.xml</param-value>
     </init-param>

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:

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

2. 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:

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

3. 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.
4. 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:

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

...

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

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.

6. 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:

     <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