Changes to Struts application code


This section describes the main differences between a servlet-based Struts application and a Struts application created for the portal environment.

 

Comparison of servlets and portlets

There are two main differences between the portlet and servlet environment that affects a Struts application in WebSphere Portal.

  1. Action processing and rendering

    All servlet processing occurs during the service() method. The Struts rendering of the page is usually immediately preceded by action processing; they are essentially part of one step. The request and response object are passed to the service() method, which writes the resulting output to the response object. The servlet-based Struts RequestProcessor is designed to complete the Struts action processing and eventually forward, based on the URI, to a JSP or another Struts action.

    Portlet processing, however, is implemented in two phases, an action phase and a rendering phase. Action processing is performed prior to rendering the display view. Only the request object is passed to the portlet during the action phase. When a Struts application is migrated to the portlet environment, some of the information that was available during the action phase, namely the request parameters, is no longer available during the rendering phase. Additionally, since rendering methods, such as doView(), can be called when the portlet page is refreshed without a new event occurring for that portlet, all information required to render the page must be available every time that method is called.

    The struts action is not invoked when moving between modes (view, edit, and configure). Only the service method is called (doView, doEdit) when switching modes. To cause the struts action to fire, place a call to that action in these methods.

    The Struts Portlet Framework provides a RequestProcessor that creates an IViewCommand. IViewCommand encapsulates the information so that the command object can be rendered at a later time, during the rendering method. IViewCommand and IViewCommandFactory are discussed in Saving information for rendering the view.

  2. URI construction

    URIs are constructed differently for portlets than for servlets. The portlet creates the URI using the PortletResponse.createURI() method. The Struts Portlet Framework has modified the tags in Struts so that they create portal links. The Struts link tags behave the same as they do in the servlet environment, but the URL is a portlet URL and the Struts URL is passed as a parameter on the URL. See Create portlet URIs for more information.

For general information about portlet action processing, see Portlet events.

 

Saving information for rendering the view

A command pattern can be used to encapsulate the rendering of the view, and the information required during this rendering (See Design Patterns: Elements of Reusable Object-Oriented Software by Gamma, Helm, Johnson, and Vlissides for more information about the command design pattern). The pattern is implemented using the IViewCommand interface.

During the actionPerformed() processing, information needed to render the view needs to be saved, including the path to the page to be displayed. Also, for a JSP in a Struts application, the associated ActionForm is normally needed in order to fill in the page. So, any ActionForm attributes need to be saved along with the path to the page to render. The attributes that are saved with a command should be limited because, since the action processing and view rendering steps involve separate requests, the command is saved in session. Since the session information may need to be serialized in a high availability environment, it is important to minimize the amount of data stored in the session wherever possible. Thus, the ActionForm is typically needed, this is saved by default.

In doView() processing, nothing needs to be known about the IViewCommand except that it should be executed. The execute method of the IViewCommand receives the request and response objects as parameters. As part of the processing, the previously saved attributes are populated into the request object that is passed in on the execute method.

Also, during the rendering phase, an execution context object (ViewCommandExecutionContext) is passed in on the execute call, which provides additional objects to help with the actual execution. You can save additional information with the view command and provide additional objects and information in the command execution context.

For more information about IViewCommand, refer to the Struts Application Framework Javadoc, located in the wp_root/dev/struts directory. Also see Use the command factory.

 

Response object

During portlet action processing the response object is not available. The methods called during Struts processing expect both a request and response. To address the need for a response object, the Struts Portlet framework creates a temporary one. Other than for calling sendError(), not write to the response object. Instead your Action should return an ActionForward object and let the Struts RequestProcessor complete the doForward set.

 

sendError() processing

The typical Struts application has no need to access the response object during the Action processing. However, when an application checks and finds some state information invalid during action processing, it is not unusual for a Struts application to report the error using the response.sendError() method. The Struts Portlet Framework intercepts the calls to sendError() and saves the error information in the session. During the later view rendering phase, this error information is found, and the error information is displayed in lieu of displaying other content for the portlet.

 

Overriding Error Response Formatter

The Struts Portlet Framework provides default formatting for error responses. The default error response formatter is com.ibm.wps.portlets.struts.plugins.DefaultErrorResponseFormatter. This formatter can be overridden from within a Struts Plug-in. You can provide your own Plug-in which sets your own subclass of DefaultErrorResponseFormatter. The TestErrorResponseFormatterPlugin is a sample plug-in that shows how this can be done. Shown below is the configuration required to load the TestErrorResponseFormatterPlugin.

 <plug-in className=
"com.ibm.wps.portlets.struts.plugins.TestErrorResponseFormatterPlugin">
 </plug-in>

 

Saving information in a bean

A typical Struts application does not write directly to the response object and instead forwards to a JSP. The JSPs should not need access to the original event information, request parameters, which initially led to the current rendering. Instead, action processing should store information in a bean from which the JSP extracts the information. As long as the bean is available during the rendering, the rendering can be repeated. Since beans, such as ActionForm, are usually stored in the original request attributes, the request attributes from the original request processing must be saved in the iViewCommand and made available when doView() is invoked.

 

Forwards and redirects

Although you can write portlets using Struts, there are some aspects of the servlet environment that you cannot do in a Struts portlet. For example, Struts provides support for redirects and forwards to other servlets. These are provided because they are functions generally available to servlets. However, these capabilities are not available in portlets because WebSphere Portal does not support forwards or redirects.

The Struts Action object returns an ActionForward object, which contains a path to be either forwarded or redirected to. WebSphere Portal does not support forward operations because the response object has already been committed. If the path is not an action, include the page using the PortletContext.include() method. If the path is an action, then a forward to the Action is simulated. Forwards to other actions can be handled by recursively sending the new Action URI through the Struts base processing of an Action. Redirects are treated in the same way as a forward. This can be implemented using PortletApiUtils class. The example below demonstrates how to implement a forward to a Struts Action from an Action or a custom tag.



PortletApiUtils portletUtils = PortletApiUtils.getInstance();
if (portletUtils != null)
{
   portletUtils.forward( page, request );
}
else
{
   pageContext.forward( page, request );
}


The PortletApiUtils.getInstance() method is called to see if a PortletApiUtils instance is available. If a non null value is returned, then the execution is inside of WebSphere Portal. In this case, the portletApiUtils.foward() method is used instead of the PageContext.forward(). Note that new signature for the PortletApiUtils has been introduced that also requires the HttpServletRequest object. In previous releases, the forward that only was passed the page has been deprecated.

The issue with forwards affects tag handlers as well. In tag handlers, it is possible to access the PageContext object and invoke the forward method. An alternative method to the PageContext.forward() is available via the PortletApiUtils class that will provide a mechanism to emulate the forward functionality.

The tags in the PortalStrutsExample were modified to use the PortletApiUtil class instead of the PageContext.forward. The tags shipped with the Struts example are CheckLogonTag, LinkSubscriptionTag, and LinkUserTag. Here is a code sample of the change.



    PortletApiUtils portletUtils = PortletApiUtils.getInstance();
    if (portletUtils != null)
    {
      // when run in a portlet use this execution path.
      Object pResponse = portletUtils.getPortletResponse( request );
      Object portletURI =
      portletUtils.createPortletURIWithStrutsURL(pResponse, url.toString() );
      // don't need to call response.encodeURL, the portletURI.toString takes care of that.
      results.append(portletURI.toString() );
    }
    else
    {
      // when run as a servlet, execute this path
      results.append(response.encodeURL(url.toString()));
    }

    

If a forward is required in a JSP, then the logic forward tag is the suggested solution. The forward tag has been modified to use the PortletApiUtils forward implementation, and is the preferred method for a forward from a JSP. The PortletApiUtils can be obtained in a JSP through java code, but obtaining the Struts ModuleConfig to prefix the path is problematic. The logic forward tag handles these issues.

For further information, see Create link tags.

 

Customize Commands

The section Comparison of servlets and portlets describes two phase processing of the portal server and the need for the IViewCommand objects that are created in the actionPerformed() phase. The Struts Portlet Framework ships several different commands that handle dynamic content, static content, XML data and error information from response.sendError(). This section describes some of the customizations that can be applied to a command, and also how to implement a command factory for creating new commands.

 

WpsStrutsViewCommand

WpsStrutsViewCommand is the base class for commands in the Struts Portlet Framework. This class provides the foundation for saving the required information, including attributes, so the command can be rendered at a later time and multiple times. WpsStrutsViewCommand limits the number of attributes saved in the command object because these objects are saved in session. The command object needs to be available any time the portlet is refreshed so the command is saved in session. There is one command per portlet mode supported.

When the command object is created, WpsStrutsViewCommand saves the request attributes that are configured to be saved. When the command is executed, it restores these attributes in the request object. An anticipated change to a command is the need for additional request attributes to be stored with the command. WpsStrutsViewCommand has two static methods that allow adding additional request attributes to save so this change can be made easily. The new attributes can be request attributes with a specific name, or attributes of a specific type.

If the rendering step requires additional attributes, then WpsStrutsViewCommand can be requested to save those attributes.

 

WpsStrutsViewJspCommand

WpsStrutsViewJspCommand is the most commonly used command in the Struts Portlet Framework and the dynamic content rendered in the JSP most likely needs request attributes in the render step. The request attributes that are saved and made available in the render step are:

Attributes with the name

Globals.MODULE_KEY
Globals.MESSAGES_KEY
Globals.ERROR_KEY

Attributes of type

ActionForm

 

Adding request attributes stored with a command

The Struts application should call WspStrutsViewCommand to add static methods before any IViewCommand objects are created. There are two ways to implement this customization of the WpsStrutsViewCommand.

 

Accessing request attributes

All request attributes that are set by the Struts action will be available when the WpsStrutsViewCommand is rendered the first time without the need to take these additional steps to save them. When a user interacts with a portlet, this defines a single request and the same request object is available to the actionPerformed() method, where the Struts action is executed, and the service() method, where the rendering occurs. If the portlet is requested to refresh the view at a later time, the attributes might not be available. The refresh may be because the user is interacting with another portlet and the request object will not have the expected attributes. If the additional attributes are required during the refresh at a later time, then WpsStrutsViewCommand should be modified to save these attributes as described above. Also, the rendering step should not rely directly on request parameters. Request parameters should be processed in the Action. However, request parameters can be saved automatically by Struts in the ActionForm. The ActionForm objects are available to the rendering phase.

 

Adding commands through the command factory

The Struts Portlet Framework uses a factory object to create commands representing the view to render based on the given input information. The view command object typically contains the URL of the object (usually a JSP) creating the output for the portlet and other information needed for the rendering. The default view command factory class is com.ibm.wps.portlets.struts.plugins.ViewCommandFactory. The factory can be overridden from within a Struts Plug-in. You can provide your own plug-in which sets your own subclass of ViewCommandFactory as needed. The TestViewCommandFactoryPlugin is a sample plug-in which shows how this can be done. Shown below is the configuration to cause the TestViewCommandFactoryPlugin to be loaded.

<plug-in className=
"com.ibm.wps.portlets.struts.plugins.TestViewCommandFactoryPlugin">
</plug-in>

The command object needs to contain the information to render the output properly. For a JSP within a Struts application, this is typically a form bean. It is possible, of course, that a particular portlet needs additional information in order to do the rendering. In such a case, you would need to add code to cause the additional information to be added to the command. The likely approach would be to override the saveAttributes() method in your subclass of the IViewCommand class. If the given URI (or perhaps type of URI) was one that would require additional information for rendering, then a new type of WpsStrutsViewCommand would need to be created which contained the needed information. The saveAttributes() method of this subclass of IViewCommand could then save the additional objects to be placed in the request object as attributes when the command was executed. To cause an existing request attribute to be saved, one could use the saveAttribute method providing the request object and name of the attribute.

 

API enhancements

Ideally, portal-specific requirements should be minimized when writing Struts Applications. There are certain areas that need to be surfaced to the developer, like tag modifications and forwards. For those developing Struts applications specifically for the portlet environment, a class is provided to hide some of the servlet details that do not map well to execution in the portal server environment.

 

StrutsAction

The com.ibm.wps.struts.StrutsAction class is a subclass of the StrutsAction class and provides an execute method that is not passed a response object. An Action in WebSphere Portal does not have a response object available and a pseudo one is supplied strictly to support response.sendError(). The StrutsAction class does not require a response object in the execute method, and provides sendError() methods to eliminate the need to use the response.sendError() method. The methods are also passed PortletRequest signature, so a cast is not required to access portal-specific objects.

The signatures for these functions in the StrutsAction class are below:

    public ActionForward execute(ActionMapping mapping, ActionForm form,
                                 PortletRequest request) throws Exception;

    public void sendError(PortletRequest portletRequest, int sc, String msg) throws IOException;

    public void sendError(PortletRequest portletRequest, int sc) throws IOException

 

WpsLookupDispatchAction and WpsDispatchAction

The WpsLookupDispatchAction and WpsDispatchAction classes offer the same functionality as their Jakarta LookupDispatchAction and DispatchAction counterparts. The Struts Portlet Framework implementation does not pass a response object in the Action methods. WpsDispatchAction extends StrutsAction, so the StrutsAction methods can be used for the sendError() implementation.

In the following getKeyMethodMap() example, the add and delete buttons each have their own methods for processing.

 protected Map getKeyMethodMap() {
      Map map = new HashMap();
      map.put("button.add", "add");
      map.put("button.delete", "delete");
      return map;
  }

The WpsLookupDispatchAction subclass implements the following two methods.

  public ActionForward add(ActionMapping mapping, ActionForm form, PortletRequest request )
          throws IOException, ServletException {
      // do add
      return mapping.findForward("success");
  }
  public ActionForward delete(ActionMapping mapping, ActionForm form, PortletRequest request )
          throws IOException, ServletException {
 *      // do delete
 *      return mapping.findForward("success");
 *  }
 *  

The Struts Portlet Framework implementation of LookupDispatchAction is not passed a response object and the request object is a PortletRequest. These two classes are a convenience for applications that are intended for the portal environment only.

 

Accessing portlet objects

There will be situations when a Struts Action needs to access objects in the Portlet API. For example, the Struts Action might need to access the PortletRequest object to get to PortletSettings. The request object passed in on the execute method of the Struts action is actually the PortletRequest object.

The Struts Action class also makes the ActionServlet class available. When the Struts application is authored with the Struts Portlet Framework, the ActionServlet class is actually a WpsActionServlet. See the Javadoc for more information about the WpsActionServlet class.

 

Sending a message

When the Struts Portlet Framework is placed on a page, the initial screen is typically configured using the Welcome File List. This configuration may not be applicable in all cases. It may also be desirable to allow another portlet to request the Struts portlet to display a specific page. The requested page can be a JSP page, a static page or a Struts action. The Struts Portlet Framework supports this feature by sending a PortletMessage to the Struts Portlet Framework. The WpsStrutsPortlet class has a message listener that will display the page sent through a DefaultPortletMessage. The action must be prepended with the string WpsStrutsConstants.ACTION_KEY.

The following code snippet shows how to send a DefaultPortletMessage to all portlets in the application. The message in the DefaultPortletMessage is the page to display.


String action = WpsStrutsConstants.ACTION_KEY + ":" + "/index.jsp" );
portletContext.send( null, new DefaultPortletMessage( action) );

The portlet message can also be used for messaging between portlets in a portlet application. The MultiStrutsApp.war example demonstrates using a WpsStrutsPortletMessage for sending messages between portlets in a portlet application. The WpsStrutsPortlet message allows specifying a Struts Action or Forward, and an attribute. The attribute is an object that will be set as an attribute in the request object with the name WpsStrutsConstants.SPF_MESSAGE_ATTRIBUTE. The Struts action can then access this object in the execute method. See the Struts Application Framework Javadoc located in the wp_root/dev/struts directory for more information about WpsStrutsPortletMessage.

For more information, see Portlet messaging.

See also