IBM WebSphere Developer Technical Journal: Executing Struts actions during the render phase of IBM WebSphere Portal

The IStrutsPrepareRender interface, a powerful new feature of the IBM® Struts Portlet Framework, makes it easy for developers to write Render Struts actions that can be executed during the render phase of IBM WebSphere® Portal. This article explains how to write Render Struts actions and how portlet processing has been changed to accommodate these actions.

Share:

Shannon Pixley, Software Engineer, IBM Research Triangle Park

Author photoShannon N. Pixley is an IT Specialist in IBM Software Services for Lotus. She specializes in portlet application development. She has a Master’s Degree in Computer Science from Rensselaer Polytechnic Institute. She lives in Raleigh, NC.



06 April 2005

Introduction

IBM® WebSphere® Portal action events are generated during the first phase, or action phase, of portlet processing. After action events are processed, the second phase of portlet processing, the render phase, begins, during which each portlet is asked to render its view.

Only a portlet that receives an action request will go through the action processing phase. Form submission, for example, is one kind of action event usually triggered by interaction with the user. Portlets can also invoke actions in other portlets on the page; these other portlets will go through the action phase as well. Portlets that do not receive action requests will not go through the action phase.

All portlets, however, go through the render phase.

There can be times when a portlet will need to update its view even when the user is interacting only with other portlets. A Struts portlet that requires updated or refreshed data each time the page renders can take advantage of Render Struts actions, which implement the new com.ibm.portal.struts.action.IStrutsPrepareRender interface in the Struts Portlet Framework in WebSphere Portal. Render Struts actions are invoked during the render phase to gather data so that it can refresh the view of a portlet. They can also enable render time updates of bean values. The execution of Render Struts actions is therefore shifted from the portlet's action phase to the render phase.

This article explains how to write Render Struts actions and how portlet processing has been changed to accommodate these actions. The article includes examples written for both the IBM legacy container and the JSR 168 container.

About the examples used in this article

The examples, figures, and Struts Portlet Framework sample code in this article are based on the IBM legacy container of IBM WebSphere Portal V5.1. The concepts presented in this article, however, also apply to the JSR 168 container. The examples included in the accompanying download file include support for both containers. (The Struts Portlet Framework included in these examples is a preview of the WebSphere Portal V5.1.0.1 release.) For more information on the examples used in this article, see About the download files.


Business need

In general, Struts actions are processed during the action phase of WebSphere Portal by WpsRequestProcessor in the Struts Portlet Framework. Struts actions are processed until a non-Struts action is found. An IViewCommand object is then created to encapsulate the necessary information for displaying the page. In a typical case:

  1. A Struts action populates a form bean and forwards to a JSP to display a page.
  2. In the Struts Portlet Framework, a WpsStrutsViewJspCommand is created and saves the path to the JSP and the form bean (if request-scoped).
  3. The IViewCommand is created and saved during the action phase of WebSphere Portal.
  4. The saved IViewCommand can then be executed each time the portlet is asked to render itself.
  5. When the WpsStrutsViewJspCommand executes, it displays the page based on the path to the JSP and the data saved in the form bean.

This process may be suitable for some Struts portlet applications, but others may have a need to refresh the data in the form bean whenever the portal page refreshes. This might happen when an action event occurs for any portlet on the page, or when the user leaves the page and returns later. The Struts Portlet Framework addresses this need with Render Struts actions, actions that can execute whenever the portlet is asked to render so that its data can be updated with each rendering.

The Render Struts action is synonymous with the notion of a page action, which is simply responsible for preparing the page for display. A page action does not rely on data input from the user. It relies instead on the requirements for displaying the page and ensures that information needed to display the page is available. Page actions need to be separated from actions that control business transactions. Provided that the action is not performing a business transaction itself, using page actions is an acceptable strategy.

Struts actions executed during the action phase have the flexibility to perform business logic. They can be used to populate form beans, change state, send messages to other portlets, participate with property broker, and so on. Render Struts actions executed during the render phase can only update objects that will be used to create the next page of the user interface. They can read back end data and update form beans. Struts actions can be daisy-chained to Render Struts actions so that they work together. The action phase Struts actions would be processed during the action phase, then the Render Struts actions would be processed during the render phase. The Struts Portlet Framework request processor handles executing the actions during the appropriate phase.

In the IBM legacy container, the response object is not available during the WebSphere Portal action phase. Because the execution of Render Struts actions is shifted to the render phase, the response object is available to the portlet. Actions needing access to the response object in the IBM legacy container may also be candidates for Render Struts actions. The Struts Portlet FrameworkLegacyTransformation portlet is an example of this use case.


When to use a Render Struts action

Since a Render Struts action executes during the render phase of WebSphere Portal, there are limitations regarding what it can do. A Render Struts action can read back end data, update the ActionForm bean to be displayed by a JSP, and statically interact with data access objects. It cannot change portlet state or write to a back end system, and it cannot interact with other portlets. Render Struts actions can access the response object.

Render Struts actions can provide good solutions for the following portlet challenges:

  • The data in your portlet does not update when you are interacting with other portlets on the page. For example, you have a Struts portlet which displays the market value of selected stocks. The only time the stock values will update is if you cause the action phase of the portlet to be invoked through the pressing of a button. In this situation, it's preferable if the information refreshes whenever the page renders.
  • The portlet is not displaying the correct information when the browser's refresh button is pressed.
  • You need access to the response object, but get an error when you try to write to it in your action.

Example 1: A simple clock

Our first example is a simple clock portlet, shown below in Figure 1.

Figure 1. A simple Struts clock portlet.
Figure 1. A simple Struts clock portlet.

The portlet consists of a welcome file, index.jsp, which contains a forward:

<logic:forward name="getTime"/>

In the Struts configuration file, the forward name is found:

Listing 1.
  <global-forwards>
    <forward name="getTime" path="/getTime.do"/>
  </global-forwards>
  
  <action-mappings>
    <action path="/getTime"
              type="com.ibm.portal.struts.example.clock.actions.GetTimeAction"
              name="ClockBean"
              scope="request">
            <forward name="success" path="/time.jsp"/>
            <forward name="failure" path="/error.jsp"/>
    </action>
  </action-mappings>

The action, GetTimeAction, determines the current time and updates the value in the ClockBean form bean. It then forwards to time.jsp, which displays the time, as well as a button to update the time.

Clicking the Update Time button results in a call to the execute method of GetTimeAction, and time.jsp displays the current time. The source for GetTimeAction is below. The time variable of ClockBean is updated with the current time.

Listing 2.
public class GetTimeAction extends Action {

  public ActionForward execute(ActionMapping mapping, 
                               ActionForm form, 
                               HttpServletRequest request, 
                               HttpServletResponse response)
  throws Exception {
    ActionForward forward = mapping.findForward ("success");
    if (form instanceof ClockBean) {
      ClockBean clock = (ClockBean) form;
      Date current = new Date();
      String timeString = DateFormat.getTimeInstance (DateFormat.LONG).format (current);
      clock.setTime (timeString);
    }
    return forward;
  }
}

Time does not update on page refresh

As seen in the above code, the clock's time will be updated in the form bean every time the execute method of GetTimeAction is called. When the portlet initially displays, the execute method is called when the welcome file forwards to this action. When the Update Time button is pressed, the GetTimeAction updates the time during the action phase. The portlet displays the time when it renders.

If there are multiple portlets on the page, then only the portlet that the user is interacting with will go through the action phase; all other portlets will only go through the render phase. The render phase may not always be preceded by the action phase; in this case the clock would not update the time. While the user is interacting with other portlets on the page, action events are not occurring for the clock portlet. The execute method of GetTimeAction is never called, and the time does not update.

Refreshing the clock

GetTimeAction simply performs the task of obtaining the current time and updating the form bean. It is a candidate for a Render Struts action because it updates information required for rendering the view. It does not change portlet state. In order to specify a Struts action as a Render Struts action, the action would implement the com.ibm.portal.struts.action.IStrutsPrepareRender interface. This change will enable the portlet to properly refresh while in the render phase.


Framework modifications

The following sections will be helpful to those familiar with the model-view-controller (MVC) design pattern and, specifically, the controller components of Struts.

org.apache.struts.action.RequestProcessor

For Struts to be used within the portal environment, certain aspects of the framework are overridden. The Struts Portlet Framework provides its own WpsRequestProcessor, which extends the org.apache.struts.action.RequestProcessor. The process method of the RequestProcessor is below. The methods in bold are overridden in the Struts Portlet Framework WpsRequestProcessor. Notice each of the opportunities where this function can exit or return. The processMapping() and processActionPerform() methods of the WpsRequestProcessor are affected by Render Struts actions.

Listing 3.
public void process(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {

   // Wrap multipart requests with a special wrapper
   request = processMultipart(request);

   // Identify the path component we will use to select a mapping
   String path = processPath(request, response);
   if (path == null) {
       return;
   }
   if (log.isDebugEnabled()) {
       log.debug("Processing a '"+request.getMethod()+"' for path '"+path+"'");
   }

   // Select a Locale for the current user if requested
   processLocale(request, response);

   // Set the content type and no-caching headers if requested
   processContent(request, response);
   processNoCache(request, response);

   // General purpose preprocessing hook
   if (!processPreprocess(request, response)) {
       return;
   }

   // Identify the mapping for this request
   ActionMapping mapping = processMapping(request, response, path);
   if (mapping == null) {
       return;
   }

   // Check for any role required to perform this action
   if (!processRoles(request, response, mapping)) {
       return;
   }

   // Process any ActionForm bean related to this request
   ActionForm form = processActionForm(request, response, mapping);
   processPopulate(request, response, form, mapping);
   if (!processValidate(request, response, form, mapping)) {
       return;
   }

   // Process a forward or include specified by this mapping
   if (!processForward(request, response, mapping)) {
       return;
   }
   if (!processInclude(request, response, mapping)) {
       return;
   }

   // Create or acquire the Action instance to process this request
   Action action = processActionCreate(request, response, mapping);
   if (action == null) {
       return;
   }

   // Call the Action instance itself
   ActionForward forward = processActionPerform(request, response, action, form, mapping);

   // Process the returned ActionForward instance
   processForwardConfig(request, response, forward);

}

To support Render Struts actions, modifications that were made to the Struts Portlet Framework include:

IStrutsPrepareRender

com.ibm.wps.struts.action.IStrutsPrepareRender is an interface that is detected by the WpsRequestProcessor. An action that implements IStrutsPrepareRender will be executed during the render phase of WebSphere Portal.

An action implementing IStrutsPrepareRender may forward to a JSP to display the portlet, or forward to another action. Keep in mind that if a Render Struts action forwards to another action(s), then the subsequent action(s) will also be executed during the render phase, so they should be Render Struts actions as well. If a Render Struts action forwards to a non-Render Struts action, a warning will be logged. Some operations, such as property broker, are not allowed during the render phase and will result in exceptions being thrown if there is an attempt to perform them during this phase.

WpsStrutsViewActionCommand

The WpsStrutsViewActionCommand class is a new IViewCommand class which represents Render Struts actions. This class saves the required information so that the command can be rendered at a later time, and extends WpsStrutsViewCommand. When the WpsRequestProcessor detects a Render Struts action, the ViewCommandFactory will create a WpsStrutsViewActionCommand command object. (Details on how a Render Struts action is detected will be discussed later.) When the WpsStrutsViewActionCommand is called upon to execute during the render phase, it calls WpsRequestProcessor.processNewActionUri so that the Render Struts action can be processed and its execute method called.

WpsStrutsConstants.STRUTS_VIEW_ACTION

This constant was added to aid in the detection of a Render Struts action. The process method of org.apache.struts.action.RequestProcessor is called twice when a Render Struts action is detected. The first time through the process method, the request attribute is set, and processMapping is interrupted and returns null, which causes the process method to return. The second time through the process method, the attribute is already set, processMapping continues by returning the mapping, and the process method of the request processor is allowed to continue.

WpsRequestProcessor.processMapping

Represented in pseudocode, this method does the following:

Listing 4.
call super.processMapping
if (action is a render struts action)
    if (request attribute WpsStrutsConstants.STRUTS_VIEW_ACTION is NOT set)
        create new WpsStrutsViewActionCommand
        return null 
return mapping

WpsRequestProcessor.processActionPerform

processActionPerform is responsible for calling the execute method of the action. First it needs to determine what type of action it is, so that it can pass the appropriate parameters:

  • If the action extends com.ibm.wps.struts.action.StrutsAction and extends IStrutsPrepareRender:

    return (((StrutsAction) action).execute (mapping, form, portletRequest, portletResponse));

    The PortletResponse is passed.

  • If the action extends StrutsAction and is not a Render Struts action:

    return (((StrutsAction) action).execute (mapping, form, portletRequest));

    The PortletResponse is not passed. The response object is unavailable during the action phase of portal.

  • If the action extends org.apache.struts.action.Action:

    return (action.execute(mapping, form, request, response));

    If the action is executed during the action phase, then the response object is a pseudo-response object and has limited use. If the action is executed during the render phase, then the response object would be available for the action.

WpsRequestProcessor.doInclude

This method was modified to add a test for a Render Struts action. If the URI passed to this method is not an action, and the request attribute WpsStrutsConstants.STRUTS_VIEW_ACTION is set, include the JSP. If the URI is an action, process the URI. If not an action, create a new IViewCommand for the URI.


Putting it all together

The Struts Portlet Framework processes Render Struts action and non-Render Struts actions differently. The next sections use the clock portlet introduced above as Example 1 for illustrating what happens in the Struts Portlet Framework when different scenarios are encountered for both types of actions. (The classes and methods in these sections are documented in the public API of the Struts Portlet Framework.) This exercise will be presented as different scenarios to show the difference in request processing:

When GetTimeAction is not a Render Struts action:

When GetTimeAction is a Render Struts action:

As mentioned, the Struts Portlet Framework request processing logic takes a different path when Render Struts actions are detected. The tables below provide the specifics:

  • Compare Scenarios 1a and 2a to see where request processing begins to take a different path.
  • Compare Scenarios 1b and 2b to see how and why the time does not change in 1b, but does get updated in 2b.
  • Compare Scenarios 1c and 2c to see the differing series of events which lead to the same outcome: the time is updated.
  • Notice when the new IViewCommands (for example, WpsStrutsViewJspCommand, WpsStrutsViewActionCommand) are created.
Scenario 1a: View the portlet on a page, GetTimeAction is not a Render Struts action
Render phase
1. The portal calls: service() method, which calls: WpsStrutsPortlet.doView(), which calls: WpsStrutsPortlet.doService().
2. The welcome file is determined to be index.jsp.
3. Create a WpsStrutsViewJspCommand to represent index.jsp.
4. Execute the command, then call WpsStrutsViewJspCommand execute() method.
5. index.jsp contains a forward, <logic:forward name="getTime"/>.
6. PortletContext include() is called for /index.jsp.
7. The forward is mapped to the URI /getTime.do.
8. WpsStrutsPortlet processNewActionUri() method is called because the URI is an action.
9. processNewActionUri calls the process() method of the request processor (see the process() method code).
10. WpsRequestProcessor processMapping() method is eventually called by the request processor process() method.
11. Call super's processMapping() to obtain the mapping.
12. The action, GetTimeAction, is not a Render Struts action.
13. WpsRequestProcessor processMapping() returns the mapping.
14. Continue through request processor process() method.
15. WpsRequestProcessor processActionPerform() is eventually called by the process() method.
16. Call the execute() method of the action.
17. Set the clock time.
18. GetTimeAction execute() forwards to "success".
19. WpsRequestProcessor processForwardConfig() is called.
20. WpsRequestProcessor doForward() called with URI = /time.jsp.
21. WpsRequestProcessor doInclude() is called.
22. URI is not an action; create a WpsStrutsViewJspCommand for /time.jsp.
23. Execute the WpsStrutsViewJspCommand representing /time.jsp.
24. PortletContext include() called for /time.jsp.
25. Clock displays the time.

Notes on Scenario 1a: When the portlet is added to a page and viewed for the first time, the portlet does not go through the action phase. A WpsStrutsViewJspCommand is created to represent the index.jsp welcome file during the render phase, and is executed during the render phase. GetTimeAction is executed. A WpsStrutsViewJspCommand is created to represent time.jsp during the render phase, and is executed during the render phase.

Scenario 1b. Refresh the page, GetTimeAction is not a Render Struts action
Render phase
1. The portal calls: service() method, which calls: WpsStrutsPortlet.doView(), which calls: WpsStrutsPortlet.doService().
2. Execute the saved WpsStrutsViewJspCommand for /time.jsp.
3. PortletContext include() called for /time.jsp.
4. Clock displays the time.

Notes on Scenario 1b: The saved IViewCommand for view mode, representing /time.jsp, is executed during the render phase. The value of the clock's time in the form remains unchanged.

Scenario 1c. Select Update Time button to generate action event, GetTimeAction is not a Render Struts action
Action phase
1. Action event /getTime.do triggers portlet's action phase.
2. WpsStrutsPortlet actionPerformed() method is called by the container.
3. WpsStrutsPortlet processActionPerformed() method is called.
4. Get the request processor and call its process() method.
5. WpsRequestProcessor processMapping() method is eventually called by the request processor process() method.
6. Call super's processMapping() to obtain the mapping.
7. The action, GetTimeAction, is not a Render Struts action.
8. WpsRequestProcessor processMapping() returns the mapping.
9. Continue through request processor process() method.
10. WpsRequestProcessor processActionPerform is eventually called by the process() method.
11. Call the execute() method of the action.
12. Set the clock time.
13. GetTimeAction execute() forwards to "success".
14. WpsRequestProcessor processForwardConfig() is called.
15. WpsRequestProcessor doForward() called with URI = /time.jsp.
16. WpsRequestProcessor doInclude() called.
17. URI is not an action, create a WpsStrutsViewJspCommand for /time.jsp.
Render phase
18. The portal calls: service() method, which calls: WpsStrutsPortlet.doView(), which calls: WpsStrutsPortlet.doService().
19. Execute the saved WpsStrutsViewJspCommand for /time.jsp.
20. PortletContext include() called for /time.jsp.
21. Clock displays the time.

Notes on Scenario 1c: The action to update the clock's time is executed during the action phase. The action forwards to time.jsp, and an IViewCommand representing time.jsp is created during the action phase. The render phase follows and the IViewCommand is executed.

Scenario 2a. View portlet on a page, GetTimeAction is a Render Struts action
Render phase
1. The portal calls: service() method, which calls: WpsStrutsPortlet.doView(), which calls: WpsStrutsPortlet.doService().
2. The welcome file is determined to be index.jsp.
3. Create a WpsStrutsViewJspCommand to represent index.jsp.
4. Execute the command, call WpsStrutsViewJspCommand execute() method.
5. index.jsp contains a forward, <logic:forward name="getTime"/>.
6. PortletContext include() called for /index.jsp.
7. The forward is mapped to the URI /getTime.do.
8. WpsStrutsPortlet processNewActionUri() method is called because the URI is an action.
9. processNewActionUri calls the process() method of the request processor (see the process() method code).
10. WpsRequestProcessor processMapping() method is eventually called by the request processor process() method.
11. Call super's processMapping() to obtain the mapping.
12. The action, GetTimeAction, is a Render Struts action.
13. Request attribute WpsStrutsConstants.STRUTS_VIEW_ACTION is not set.
14. Create a WpsStrutsViewActionCommand for uri /getTime.do.
15. WpsRequestProcessor processMapping() returns null.
16. Request processor process() method returns.
17. Execute the command, call WpsStrutsViewActionCommand execute() method.
18. execute() calls WpsRequestProcessor processNewActionUri() method.
19. processNewActionUri calls the process() method of the request processor (see the process() method code).
20. WpsRequestProcessor processMapping() method is eventually called by the request processor process() method.
21. Call super's processMapping() to obtain the mapping.
22. The action, GetTimeAction, is a Render Struts action.
23. Request attribute WpsStrutsConstants.STRUTS_VIEW_ACTION is set.
24. WpsRequestProcessor processMapping() returns the mapping.
25. Continue through request processor process() method.
26. WpsRequestProcessor processActionPerform() is eventually called by the process() method.
27. Call the execute() method of the action.
28. Set the clock time.
29. GetTimeAction execute() forwards to "success".
30. WpsRequestProcessor processForwardConfig() is called.
31. WpsRequestProcessor doForward() called with URI = /time.jsp.
32. WpsRequestProcessor doInclude() called.
33. Request attribute WpsStrutsConstants.STRUTS_VIEW_ACTION is set.
34. PortletContext include() called for /time.jsp.
35. Clock displays the time.

Notes on Scenario 2a: When the portlet is added to a page and viewed for the first time, the portlet does not go through the action phase. A WpsStrutsViewJspCommand is created to represent the index.jsp welcome file during the render phase, and is executed during the render phase. Since it forwards to a Render Struts action, a WpsStrutsViewActionCommand is created during the render phase. The WpsStrutsViewActionCommand executes, and calls GetTimeAction to update the clock's time.

Scenario 2b. Refresh the page, GetTimeAction is a Render Struts action
Render phase
1. The portal calls: service() method, which calls: WpsStrutsPortlet.doView(), which calls: WpsStrutsPortlet.doService().
2. Call execute() for the saved WpsStrutsViewActionCommand for /getTime.do.
3. execute() calls WpsRequestProcessor processNewActionUri() method.
4. processNewActionUri calls the process() method of the request processor (see the process() method code).
5. WpsRequestProcessor processMapping() method is eventually called by the request processor process() method.
6. Call super's processMapping() to obtain the mapping.
7. The action, GetTimeAction, is a Render Struts action.
8. Request attribute WpsStrutsConstants.STRUTS_VIEW_ACTION is set.
9. WpsRequestProcessor processMapping() returns the mapping.
10. Continue through request processor process() method.
11. WpsRequestProcessor processActionPerform is eventually called by the process() method.
12. Call the execute() method of the action.
13. Set the clock time.
14. GetTimeAction execute() forwards to "success".
15. WpsRequestProcessor processForwardConfig() is called.
16. WpsRequestProcessor doForward() called with URI = /time.jsp.
17. WpsRequestProcessor doInclude() called.
18. Request attribute WpsStrutsConstants.STRUTS_VIEW_ACTION is set.
19. PortletContext include() called for /time.jsp.
20. Clock displays the time.

Notes on Scenario 2b: The saved IViewCommand for view mode, WpsStrutsViewActionCommand, is executed during the render phase. GetTimeAction executes and updates the clock's time.

Scenario 2c. Click Update Time button to generate action event, GetTimeAction is a Render Struts action
Action phase
1. Action event /getTime.do triggers portlet's action phase.
2. Start portlet's action phase.
3. WpsStrutsPortlet actionPerformed() method is called by the container.
4. WpsStrutsPortlet processActionPerformed() method is called.
5. Get the request processor and call process() method.
6. WpsRequestProcessor processMapping() method is eventually called by the request processor process() method.
7. Call super's processMapping() to obtain the mapping.
8. The action, GetTimeAction, IS a Render Struts action.
9. Request attribute WpsStrutsConstants.STRUTS_VIEW_ACTION is not set.
10. Create a WpsStrutsViewActionCommand for uri /getTime.do.
11. WpsRequestProcessor processMapping() returns null.
12. Request processor process() method returns.
Render phase
13. The portal calls: service() method, which calls: WpsStrutsPortlet.doView(), which calls: WpsStrutsPortlet.doService().
14. Call execute() for the saved WpsStrutsViewActionCommand for /getTime.do.
15. execute() calls WpsRequestProcessor processNewActionUri() method.
16. processNewActionUri calls the process() method of the request processor (see the process() method code).
17. WpsRequestProcessor processMapping() method is eventually called by the request processor process() method.
18. Call super's processMapping() to obtain the mapping.
19. The action, GetTimeAction, IS a Render Struts action.
20. Request attribute WpsStrutsConstants.STRUTS_VIEW_ACTION is set.
21. WpsRequestProcessor processMapping() returns the mapping.
22. Continue through request processor process() method.
23. WpsRequestProcessor processActionPerform is eventually called by the process() method.
24. Call the execute() method of the action.
25. Set the clock time.
26. GetTimeAction execute() forwards to "success".
27. WpsRequestProcessor processForwardConfig() is called.
28. WpsRequestProcessor doForward() called with URI = /time.jsp.
29. Request attribute WpsStrutsConstants.STRUTS_VIEW_ACTION is set.
30. PortletContext include() called for /time.jsp.
31. Clock displays the time.

Notes on Scenario 2c: The action to update the clock's time is a Render Struts action. A WpsStrutsViewActionCommand is created during the action phase. The render phase follows and the IViewCommand is executed. GetTimeAction executes and updates the clock's time.


Example 2: XSL transformation

The response object is not available during the action phase of WebSphere Portal. The IStrutsPrepareRender interface enables implementing a Struts action that will be executed in the render phase of WebSphere Portal, and therefore will have a response object that can be written to when it executes. The Struts Portlet FrameworkLegacyTransformation portlet demonstrates using an action that implements the IStrutsPrepareRender interface to perform an XSL transformation. The results of the transformation are written to the response object, and the portlet displays the results.

Figure 2. Struts XSL transformation portlet
Figure 2. Struts XSL transformation portlet

The portlet's initial page, index.jsp, contains a link to the Struts Main Configuration Tour:

Listing 5.
<html:link page="/strutsexplain.do">
   <bean:message key="link.main.struts.config"/>
</html:link>

When the user clicks on the link, the request URI is used to find a match in the ActionMappings, shown below:

Listing 6.
<action path="/strutsexplain" 
     type="com.ibm.portal.struts.example.transform.actions.ApplyTransformAction"
     className="com.ibm.portal.struts.example.transform.TransformMapping">
     <set-property property="stylesheetUri" value="/WEB-INF/StrutsConfigExplain.xsl"/>
     <set-property property="xmlDocument" value="/WEB-INF/struts-config.xml"/>
     <set-property property="returnUri" value="/index.jsp"/>
     <set-property property="returnUriLabel" value="Home"/>
     <forward name="return" path="/index.jsp"/>
</action>

The action specifies ApplyTransformAction as the object type. This class will process the request and return an ActionForward, indicating where control is to be passed. TransformMapping is the class name of the ActionMapping subclass to use for this action mapping object.

Listing 7.
public class TransformMapping extends ActionMapping {
   private String stylesheetUri = null;
   private String xmlDocument = null;
   private String returnUri = null;
   private String returnUriLabel = null;
   
  // Getters and setters for each of the private member variables above
   
   public TransformMapping()   { }
   public void execute (HttpServletRequest request) { }
}

TransformMapping has four private member variables. The values for these variables are supplied in the Struts configuration using the <set-property> tag. The ApplyTransformAction class is a Render Struts action so that it can write to the response object:

public class ApplyTransformAction extends Action implements IStrutsPrepareRender

The action class contains a method signature for the execute method in which the TransformMapping is passed:

Listing 8.
public ActionForward execute (TransformMapping mapping,
                             ActionForm form,
                             HttpServletRequest request,
                             HttpServletResponse response)
throws Exception {

The action will apply the stylesheetUri to the xmlDocument and the result will be displayed in the portlet. The code below demonstrates how the result is written to the response. Since the portlet is using the response to display the results, the action returns null.

Listing 9.
        PrintWriter outStream = response.getWriter();
        outStream.write (transformResult);
        outStream.close ();
        return null;

The action also creates a link in the resulting markup to return to the portlet's initial page. The properties returnUri and returnUriLabel are used for this purpose. For more details on this example, the sample's code and a readme file are available within the WAR file in the download file included with this article.


Conclusion

The IStrutsPrepareRender interface is a powerful new feature in the IBM Struts Portlet Framework that enables Struts portlet developers to easily write Struts actions that will be executed during the render phase of IBM WebSphere Portal. This article explained how to create Render Struts actions and the changes made to the Struts Portlet framework to accommodate these actions. The article also offered scenarios and code samples to illustrate these actions for both the IBM legacy container and the JSR 168 container.


About the download files

There may be slight differences in the Struts Portlet Framework packages or class names, depending on which container the portlet is written for: the IBM legacy container or the JSR 168 container. Javadoc is available for all Struts Portlet Framework classes referenced in this article in the <WPS_HOME>/doc/Javadoc directory of WebSphere Portal V5.1, or in the downloadable ZIP file in the WebSphere Portal and Lotus Workplace Catalog (see Resources).

Portlets running on WebSphere Portal V4.2.x and later, using an older version of the Struts Portlet Framework, can be migrated to use a new version that includes support for Render Struts actions. Both WebSphere Portal V5.1 and the WebSphere Portal and Lotus® Workplace Catalog contain a version of the Struts Portlet Framework which supports Render Struts actions. The current catalog version, Struts Portlet Framework 5.0.3, contains support for the IBM legacy container only. The Struts Portlet Framework included with WebSphere Portal V5.1 contains support for both the IBM legacy container and the JSR 168 container. The examples included with this article in the download file include support for both containers. The Struts Portlet Framework included in these examples is a preview of the WebSphere Portal V5.1.0.1 release.

Each of the samples contains the requirements for building a Struts portlet application, including a collection of JAR files, TLD files, and the org.apache.commons.logging.LogFactory file. These files are stored in the WEB-INF/lib, WEB-INF/tld, and META-INF/services directories, respectively, of a Struts portlet application. There are currently three sample portlets for the IBM legacy container which use Render Struts actions:

  • Struts Portlet FrameworkLegacyClock.war
  • Struts Portlet FrameworkLegacyStockQuote.war
  • Struts Portlet FrameworkLegacyTransformation.war

The corresponding samples for the JSR 168 container are:

  • Struts Portlet FrameworkStandardClock.war
  • Struts Portlet FrameworkStandardStockQuote.war
  • Struts Portlet FrameworkStandardTransformation.war

All source code is supplied within each code sample.


Download

DescriptionNameSize
Code sampleRenderStrutsActionsExamples.zip  ( HTTP | FTP )7 MB

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into WebSphere on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere
ArticleID=56976
ArticleTitle=IBM WebSphere Developer Technical Journal: Executing Struts actions during the render phase of IBM WebSphere Portal
publish-date=04062005