Changes to Struts application code

 

+

Search Tips   |   Advanced Search

 

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 event phase and a render phase. Action processing is performed prior to rendering the display view. Only the request object is passed to the portlet during the event phase. When a Struts application is migrated to the portlet environment, some of the information that was available during the event phase, namely the request parameters, is no longer available during the render 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 programmatically using the PortletResponse object. 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 Creating 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 action 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 render phase, an execution context object (ViewCommandExecutionContext) is passed in on the execute call, which provides additional objects to help with the actual execution. We 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 Javadoc for the com.ibm.portal.struts.command package. The product Javadoc is located at portal_server_root/doc/Javadoc/api_docs/ directory. Also see Using the command factory.

Updating ActionForm in the render phase

Some Struts applications may need to refresh the beans for the render phase every time the portlet renders. If the user is interacting with the portlet and clicking action links then the portlet will have an action phase and then followed by a render phase. The typical scenario is the case where an action phase does not precede the render phase. For example, if a user is interacting with another portlet on the page, then only that portlet has an action phase. The other portlets on the page may need to refresh the data to be rendered. The Struts action that implements the IStrutsPrepareRender action can read data from the model and update the ActionForm bean associated with the JSP through the information available through the ActionMapping. The page controller action is executed in the render phase of the portal server and therefore cannot change portlet state or write to the model. It also cannot interact with other portlets using Property Broker or portlet messaging. The collaboration between portlets must occur during the action phase of the portal server.

The IStrutsPrepareRender interface is way to indicate that a Struts action should be called in the render phase of portal, for the specific task of refreshing the data to be displayed. When the Struts Portlet Framework Request Processor detects a Struts Action that implements the IStrutsPrepareRender in the action phase, the request processing is stopped and the ViewCommandFactory is called to create a WpsStrutsViewActionCommand object. The WpsStrutsViewActionCommand object is then executed in the render phase of the portal, at which time the request processor will be called again to execute the Struts action. A Struts action implementing IStrutsPrepareRender may forward to another Struts action that implements the Prepare-Render interface or to the JSP that renders the view. To see an example of how to use the IStrutsPrepareRender interface, see the Stock Quote portlet or Clock portlet shipped with WebSphere Portal.

Clock Portlet Sample:

The Clock portlet is a simple portlet that displays the system time. The typical implementation of this portlet would refresh the time when the refresh button is clicked, but would display the saved time if the clock portlet was refreshed without the refresh button clicked. This example demonstrates how this portlet can be implemented so that it refreshes the time every time the portlet is rendered, regardless if the refresh button is clicked. The GetTimeAction Struts action simply performs the task of obtaining the current time and updating the ActionForm bean. This action is a candidate for a Prepared Render Struts action because it does not change the portlet state, only refreshes the Struts ActionForm bean. A Struts Action is specified to be a Prepare Render Struts Action by implementing the IStrutsPrepareRender interface. This allows the portlet to properly refresh the Struts ActionForm bean with the current time while in the render phase. The sample for the legacy container is named SPFLegacyClock.war, and the sample for the standard container is called SPFStandardClock.war. Both clock samples are shipped with WebSphere Portal.

    public class GetTimeAction extends Action 
                               implements IStrutsPrepareRender     {
      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;
      }
    }

Stock Portlet Sample:

The Stock Quote portlet is a Struts application that allows configuring companies in the edit portlet mode, and then the Stock Quotes for the portlet are displayed in the view mode. The quotes are supplied by a StockService, which returns random numbers, but the StockQuoteService could be replaced by a real service. The Stock Quote sample also uses a Struts Action that implements the IStrutsPrerpareRender so the stock quotes are refreshed even if a request phase does not precede the render phase.

public class RefreshAction extends Action implements IStrutsPrepareRender {
  // ------------------------------------------------ Instance Variables 
   /**     
    * The Log instance for this application.     
    */    
    private Log log =       
       LogFactory.getLog(this.getClass()); 

  // ------------------------------------------------- Public Methods    /**     
    * This action will refresh the stock quotes and then forwards to an     * ActionForward for displaying the quotes.     
    *      
    * @param mapping The ActionMapping used to select this instance     * @param form The optional ActionForm bean for this request (if any)
    * @param request The HTTP request you are processing     
    * @param response The HTTP response you are creating     
    *     
    * @exception Exception if the application business logic throws     *  an exception     
    */
    public ActionForward execute(ActionMapping mapping,
                         ActionForm form,             
                         HttpServletRequest request,             
                         HttpServletResponse response)      
           throws Exception 
   {
      HttpSession httpSession = request.getSession();       
        //---------------------------------------------------------
        // Get the SubscribedNewsgroupsBean. This should have been 
        // put in the session earlier       
        //---------------------------------------------------------
        SubscribedCompaniesBean subscribedCompaniesBean = 
          ( SubscribedCompaniesBean )              
            httpSession.getAttribute( Constants.SUBSCRIBED_COMPANIES );
      
        ActionForward actionForward = mapping.findForward("welcome");
        if ( subscribedCompaniesBean != null )       
        {          
          log.debug("Refresh quotes");
          
          Collection companies = subscribedCompaniesBean.getCompanies();
          if ( companies != null )          
          {             
            StockQuoteService stockQuoteService = 
                              new StockQuoteService();
            Iterator iterator = companies.iterator();             
            while ( iterator.hasNext() )             
            {                
              CompanyBean companyBean = ( CompanyBean ) iterator.next();
              try                
              {                   
                log.debug("Get quote for company " + 
                                             companyBean.getSymbol() );
                float quote = 
                      stockQuoteService.getQuote(companyBean.getSymbol());
                companyBean.setQuote( Float.toString( quote) );
              }                
              catch ( Exception ex ) 
              { 
                companyBean.setQuote( "---");
              } 
              subscribedCompaniesBean.addCompany(companyBean.getName(), 
                                                          companyBean ); 
            }
          }
        } 
        return actionForward;   
   } 
}

 

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(), we should not write to the response object. Instead the 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 render 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. For standard portlets, the default error response formatter is com.ibm.portal.struts.plugins.DefaultErrorResponseFormatter. For IBM portlets, it is com.ibm.wps.portlets.struts.plugins.DefaultErrorResponseFormatter. This formatter can be overridden from within a Struts Plug-in. We can provide our own Plug-in which sets your own subclass of DefaultErrorResponseFormatter. The SPFLegacyPlugins.war and SPFStandardPlugins.war are examples of how to implement a custom error response formatter.

 

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 we can write portlets using Struts, there are some aspects of the servlet environment that we 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().

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 Creating link tags.

 

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

Struts View command

The Struts View command is the base class for commands in the Struts Portlet Framework.

  • Standard portlets: com.ibm.portal.struts.command.StrutsViewCommand

  • IBM portlets: com.ibm.wps.portlets.struts.WpsStrutsViewCommand

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. The Struts View command 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, Struts View command 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. The Struts View command has two static methods that allow adding additional request attributes to save so this change can be made . 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 The Struts View command can be requested to save those attributes.

Struts View Jsp command

The Struts View Jsp command is the most commonly used command in the Struts Portlet Framework.

  • Standard portlets: com.ibm.portal.struts.command.StrutsViewJspCommand

  • IBM portlets: com.ibm.wps.portlets.struts.WpsStrutsViewJspCommand

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 the Struts View command to add static methods before any IViewCommand objects are created. There are two ways to implement this customization of the Struts View command.

  • Subclass WpsStrutsPortlet and implement the init() method. The WpsStrutsPortlet subclass then uses the following WpsStrutsViewCommand static methods.

       addAttributeNameToSave(String attributeName)
       addAttributeTypeToSave(Class attributeType)
    

  • Use a Struts plugin. The Struts Plugin is initialized when the configuration for the Struts module is read. The plugin can be used to add the attributes that are needed by a command. Here is an example plugin definition from the Struts configuration file.

    <plug-in      className="com.ibm.struts.sample.plugins.ExampleAddAttributePlugin">
    </plug-in>
    

    Here is an example of the plugin's init() method:

        /**
         * Add attributes to save.
         *
         * @param config The ModuleConfig for the owning modules      * @param servlet The ActionServlet      *
         * @exception ServletException if we cannot configure 
         * ourselves correctly      */
        public void init(ActionServlet actionServlet, ModuleConfig config) 
        throws ServletException 
        {
            // Call StrutsViewCommand.addAttributeNameToSave for each         // request attribute you wish to make available to the code 
            // that renders the page. We can either specify the name 
            // of the attribute or the class         // type of attributes to save.
            StrutsViewCommand.addAttributeNameToSave("testAttribute");
        }
    
    

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

 

Add commands through the command factory

The Struts Portlet Framework uses the ViewCommandFactory object to create the IViewCommand objects for 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.

  • For standard portlets, the default view command factory class is com.ibm.portal.struts.plugins.ViewCommandFactory.

  • For IBM portlets, the default view command factory class is com.ibm.wps.portlets.struts.plugins.ViewCommandFactory.

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 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 the 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, the saveAttribute method can be used to provide the request object and name of the attribute.

The SPFLegacyPlugins.war and SPFStandardPlugins.war are examples of how to implement a custom ViewCommandFactory.

Controlling where commands are stored using the Command Manager Factory

The CommandManagerFactory allows a Struts application to specify where the IViewCommand objects are stored. The Command Manager is pluggable through the Command Manager Factory interface.

  • Standard portlet container: The Command Factory plugin is typically configured in the portlet deployment descriptor.

  • IBM portlet container: The Command Factory plugin is configured in the web deployment descriptor.

The default Command Manager Factory is the SessionCommandManagerFactory which stores the IViewCommands in the portlet session. The SessionCommandManagerFactory is the default command manger factory and recommended for portlets.

The SPFLegacyCommandManger.war and SPFStandardCommandManager.war are examples of how to implement a custom CommandManagerFactory.

 

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 StrutsAction class provides an execute method that is passed portlet objects instead of servlet objects so a cast is not required to access portal-specific objects. The signatures for functions in the StrutsAction class are below:

Standard portlets

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

public ActionForward execute(ActionMapping mapping, 
                     ActionForm form, ActionRequest request, 
                     ActionResponse response) throws Exception;

public ActionForward execute(ActionMapping mapping, 
                     ActionForm form, RenderRequest request, 
                     RenderResponse response) throws Exception;

public void sendError(ActionRequest request, 
            int sc, String msg) throws IOException;   
   
public void sendError(RenderRequest request, 
            int sc, String msg) throws IOException;   
   
public void sendError(ActionRequest request, int sc ) 
                                             throws IOException;   
   
public void sendError(RenderRequest request, 
                                    int sc ) throws IOException;

IBM portlets

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

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

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

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

LookupDispatchAction and DispatchAction

The WpsLookupDispatchAction and WpsDispatchAction classes offer the same functionality as their Jakarta LookupDispatchAction and DispatchAction counterparts.

  • Standard portlets

     com.ibm.portal.struts.action.LookupDispatchAction  com.ibm.portal.struts.action.DispatchAction 

  • IBM portlets

     com.ibm.wps.struts.action.WpsLookupDispatchAction  com.ibm.wps.struts.action.WpsDispatchAction 

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

 

Access 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 or PortletPreferences, depending on the container. The PortletApiUtils should be used to obtain the PortletRequest object from the HttpServletRequest object.

PortletApiUtils portletUtils = PortletApiUtils.getUtilsInstance();
    
if (portletUtils != null) 
{

   PortletRequest portletRequest = (PortletRequest) 
   portletUtils.getPortletRequest( request );
}
 

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 the WPActionServlet. For more information see the Javadoc for:

Standard portlet container com.ibm.portal.struts.portlet.WpActionServlet class information.
IBM portlet container com.ibm.wps.portlet.struts.WpsActionServlet class information.

 

Sending a message

The Struts examples use the Property Broker for sending messages from one portlet to another. For more information about the property broker, see the property broker programming model section of the Concepts of cooperative portlets section.

The Struts Portlet Framework ships the SPFLegacyMultipleModules, and SPFStandardMultiplModules as examples of using the property broker and Struts Portlet Framework

 

Related information