Skip to main content

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

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

All information submitted is secure.

  • Close [x]

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.

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

All information submitted is secure.

  • Close [x]

developerWorks Community:

  • Close [x]

Applying the State Pattern to WebSphere Portal V5 Portlets: Part 2. Implementation

Tim Hanis (hanistt@us.ibm.com), Senior Software Engineer, IBM
Tim Hanis is a senior software engineer for WebSphere Portal at IBM Research Triangle Park Lab in Raleigh, North Carolina.

Summary:  This article, the second in a two part series, describes the implementation of a portlet using the state pattern that was introduced in part 1.

Date:  03 Dec 2003
Level:  Intermediate

Activity:  4193 views
Comments:  

Introduction

This article supercedes an article by a similar name. The implementation details described in this paper, and the example source code, have been updated to be consistent with the Portlet API in IBM ® WebSphere® Portal Version 5.0 (hereafter called WebSphere Portal). References to deprecated methods or classes have also been removed. Specifically, the pattern implementation has been modified to remove references to the deprecated class org.apache.jetspeed.portlet.DefaultPortletAction, and to these deprecated methods:

  • org.apache.jetspeed.portlet.PortletURI.addAction(PortletAction)
  • org.apache.jetspeed.portlet.event.ActionEvent.getAction()

The example implementation was also updated to use WebSphere Portal V5 development tooling, and is now a J2EETM 1.3 application.

Part 1 provided an overview for using the state pattern to develop portlets for IBM ® WebSphere® Portal. Part 2 describes a specific implementation of a sample portlet that uses the state pattern. The portlet is a simple portlet that maintains a list of contacts (such as an address book) for a user. While this is a contrived example, it implements a very common application pattern. Sometimes referred to as "search and browse", it provides a main view that is a summary collection of items which lets the user select an item and review details about that selection. Standard CRUD (create, retrieve, update and delete) operations are available. The contact list is persisted in a database.

In this paper, you see how to implement the fully functional contacts list portlet, focusing primarily on the state pattern implementation. Other portlet requirements, such as database access, exception handling, and configuration parameters are mentioned briefly. You see how to use IBM WebSphere Studio (hereafter called Studio) to create the portlet; however, you could also create the portlet without using Studio.

The following products are used to create the sample portlet:

The complete implementation for this sample is provided in the download.

The state pattern design discussed here provides a good approach to portlet application design to help organize page transitions and to implement a separation of responsibilities in the Model-View-Controller (MVC) design pattern. If you choose to program their portlets using the IBM Portal API you need to use some an approach like this.

This paper describes a pattern implementation. The recommended framework solution for portlet development is Struts. The Jakara Struts project, an Apache Software Foundation open-source project, provides a framework to address these same issues of MVC design and page control flow. It also provides support for exception handling, error handling, input validation, forms, and internationalization. Applications can be written using the Struts framework and deployed as a portlet. For a corresponding example implementation using Struts, see Developing and Deploying a Struts Application as a WebSphere Portal V5 Portlet.


A quick review of the state pattern

The StateManagerPortlet is the main portlet class and is portlet-independent. It serves as a dispatcher to both action and state classes where the portlet specific controller code resides. Classes that extend the Action abstract class implement an actionPerformed method, which performs any controller function required to implement the specific action request behavior.

Classes that implement the State interface implement a performView method, which is called from the service method of the StateManagerPortlet. It contains the code that would normally reside in a portlet's doView, doEdit, or doConfigure method for a given request. This code is specific to one functional request, for a specific mode, and avoids all of the additional control-logic clutter to determine the requested action.

Applying the state pattern results in a clean implementation. It separates specific actionPerformed and service method implementations in different classes, and manages state transitions without adding code to explicitly perform that function. As you move between portlet modes, you return to the last state visited for a given mode. You can easily determine where and when you want data to be retrieved from the source, and when it should be retrieved from cache. Because actionPerformed methods are not invoked on portal page refreshes, you can place data access code in the action state and cache them there. The state classes can use data from cache to avoid multiple trips to get data when the portal page refreshes.

Setting up the database

First, create a database table to hold the contact list data. To create the table, run the following SQL command from a DB2 command line. Ssubstitute the schema name of your choice, but the table and column names are fixed. You can either create a new database (dataspace) for this table or add it to an existing one. If you use DB2, establish a connection to the appropriate database and then copy and run the following command in the Command Center.

CREATE TABLE "DB2ADMIN"."CONTACTS" ("OWNER" VARCHAR(64) NOT NULL,
"OID" VARCHAR(64) NOT NULL PRIMARY KEY, "FIRST_NAME" VARCHAR(32)
NOT NULL, "LAST_NAME" VARCHAR(32) NOT NULL , "EMAIL" VARCHAR(128)
NOT NULL, "TITLE" VARCHAR(64), "COMPANY" VARCHAR(64), "BUS_PHONE"
VARCHAR(32), "MOBILE_PHONE" VARCHAR(32), "FAX_PHONE" VARCHAR(32),
"WEB" VARCHAR(128), "ADDRESS1" VARCHAR(32), "ADDRESS2" VARCHAR(32),
"CITY" VARCHAR(32), "STATE" VARCHAR(2), "ZIP" VARCHAR(10), "COUNTRY"
VARCHAR(32) )

Optionally, add an entry into the contacts list table so that you can test your portlet more easily as you develop it. You can execute the following SQL command to create a new entry. If you want to log in to the portal with a userID other than wpsadmin for testing purposes, then substitute this userID for wpsadmin in the SQL statement below.

insert into contacts (owner, oid, first_name, last_name, email,
company, mobile_phone) values ('wpsadmin', '01', 'Tim', 'Hanis',
'hanistt@us.ibm.com', 'IBM', '919-555-9480')
 

Next, create a data source in WebSphere Application Server for the database in which you created the contacts table. This portlet is packaged as a J2EE 1.3 application so you need to specify an Application Server V5 data source. J2EE Level 1.3 includes a Servlet Specification level of 2.3 and a JSP Specification level of 1.2.

  1. In Application Server use the DB2 Legacy CLI-based Type 2 JDBC Driver JDBC driver and specify a Data Source, instead of the Data Source (Version 4).
  2. In the Application Server Administrative Console, select Resources =>JDBC Providers.
  3. If you already have the DB2 Legacy CLI-based Type 2 JDBC Driver installed, then select it. Otherwise, add it by selecting New and completing the next page.
  4. On the DB2 Legacy CLI-based Type 2 JDBC Driver page, ensure that the classpath is set to the correct location of the db2java.zip file.
  5. Select Data Sources, and then select New to create a new V5 data source. Specify a data source name. For the JNDI name, prefix the name with a jdbc/ sub context.
    For example, if you want a data source jndi name of aim, enter jdbc/aim. The portlet will prepend the jdbc/ sub context when attempting to a JNDI name lookup.
  6. If your database requires a userid and password for authentication, specify an authentication alias in Component-managed Authentication Alias.
  7. If you do not already have an alias defined, you can create one in Security => JAAS Configuration => J2C Authentication Data.
  8. Be sure to save the configuration changes and test the data source connection.

Figure 1. WebSphere Application Server Administrative Console
Screen capture of the WebSphere Application Server Administrative Console

Setting up the development environment

If you use WebSphere Studio to create this portlet, the following JAR files must be available on the build path in order for the portlet code to compile successfully. If you use the Portal Toolkit in Studio and create a Portlet Application Project, then the build classpath will be set up automatically. In either case, you can create a web project or portlet application project and then import the WAR file in the download to load this portlet application in Studio.

If you are not using the Portal Toolkit or Studio, you can find these files in <WAS_ROOT>/lib and <WPS_ROOT>/shared/app.

This setup step is required for development time only because all of these JAR files are available when the portlet application is deployed to the WebSphere Portal environment. You can install the portlet war file in Portal without making modifications.

  • SERVERJDK_50_PLUGINDIR/jre/lib/rt.jar
  • WAS_50_PLUGINDIR/lib/dynacache.jar
  • WAS_50_PLUGINDIR/lib/j2ee.jar
  • WAS_50_PLUGINDIR/lib/servletevent.jar
  • WAS_50_PLUGINDIR/lib/ivjejb35.jar
  • WAS_50_PLUGINDIR/lib/runtime.jar
  • WAS_50_PLUGINDIR/lib/ras.jar
  • WAS_50_PLUGINDIR/lib/naming.jar
  • WAS_50_PLUGINDIR/lib/utils.jar
  • WPS_V5_PLUGINDIR/portlet-api.jar
  • WPS_V5_PLUGINDIR/wpsportlets.jar
  • WPS_V5_PLUGINDIR/wps.jar

The contacts list portlet

The sample portlet, ContactsList, maintains a list of contacts, such as a simple address book. In edit mode, the user can view a list of contacts, see detail on a selected contact, and create, delete, or modify a contact. In configuration mode, the user can set database access information. Although, the available source code provides the complete implementation including configuration mode, for the purpose of this design, you only focus on the view and edit mode implementations.

From a visual perspective, an implementation of the contacts list portlet can have the following views (pages):

  • Main view - Displays the list of contacts with an option to select one contact for more information
  • Detail view - Displays the selected contact's information
  • Main edit view - Displays the list of contacts with options to add, delete, or modify
  • Add contact view - Displays a form to get the contact information to be added
  • Modify contact view - Displays a similar form to add but with the existing data provided for modification

To take advantage of authorization list control in the portlet, assume you want to have the summary list and detail views available to users with view permission for the portlet, and the add, edit, and delete capabilities available to users with edit permission. To enable these authorizations, the summary list and detail view pages are controlled from the portlet view mode. The remaining pages will be available in edit mode.

Consider the states and actions that make up this portlet. Think of an action as a user function or behavior that you want to implement for the portlet. For example, you might want an action to add a contact, or to show a particular view. Think of a state as the condition of the portlet after the user action has been completed; the state also represents the visual component. For example, the action of adding a new contact might result in the state of the portlet in edit mode, displaying the list of contacts.

Now, think of how you want the portlet to look visually. In this example, a main view page lists the contacts the user has created. This page lets users select one of the contacts to see additional detail about this person.


Figure 2. Main list view
Main list view

Next, look at how the portlet enables users to modify, add, and delete contacts from the list. The main edit page lists the contacts and lets the user modify, add, or delete a selection.

To modify a contact, the user sees a detail page where he or she can update any of the existing attributes of this contact entry. After completing the modifications, the user is returned to the main view page. Similarly, the user can add a new contact. A detail page lets the use enter and save the attributes for the new entry. After saving, the user is returned to the main page. The portlet does not implement another view for deleting; instead, the user simply selects an entry and deletes it. After deleting the selection, the user sees the same main page with the contact list properly refreshed.

Contact list actions

Consequently, you will have the following actions:

  • Show the main list view of all contacts
  • Show the detail view for a selected contact
  • Show the edit view list of contacts
  • Show the view to add a contact
  • Add a contact
  • Show the view to modify a contact
  • Modify a contact
  • Delete a contact

Contact list states

Executing these actions results in one of the following states:

  • Main View
  • Detail View
  • Main Edit View
  • Add Contact View
  • Modify Contact View

Now, you can determine the state transitions available for this application. The transitions are managed by applying the appropriate actions to the current state, which results in the application entering another specific state. With this level of understanding of the portlet, you can create the application using the state pattern.


Figure 3. State transitions available for this application
A state diagram

Now consider how everything works together with a state pattern implementation. First, examine the main portlet class, StateManagerPortlet, which is displayed below. You can see that it has basically two methods that do most of the work. The work, in this case, involves dispatching to either the appropriate action class or state class. The action classes are called from the actionPerformed method so they are invoked as a result of a user action,such as clicking on a link. The state class performViewmethod is invoked when the portlet class service method gets called.

Keep in mind that this logic flow starts from a user action on the user interface (UI). For example, the user clicks on a specific contact on the main list view to retrieve the details of that selected item. How does the logic flow from here?

The UI is defined as follows: the user action (clicking on a contact entry) has a link defined through an associated HREF on an anchor tag or an action on a form tag. Whichever way this is implemented, the clicking action invokes an HTTP request to a URL. When the developer builds the JSP for the UI of the main view, he or she associates the link with a PortletURI and adds a specific action class to implement the behavior of the user action to the PortletURI by using the standard addAction API. In this example, the developer creates an action class called DetailActionView, which is responsible for displaying the detail view of the selected contact. This class must then be associated with that PortletURI. We use the portlet taglib to do this by adding the name of the Action class to the PortletURI. Our StateManagerPortlet class is then responsible to interrogate that name, get an instance of that class, and invoke its actionPerformed method.

The code below is the base functional implementation of the StateManagerPortlet class. It is a clean implementation and very generic. The code also shows the service method for this class, which will be discussed shortly.

Listing 1. StateManagerPortlet Class

public class StateManagerPortlet extends AbstractPortlet
   implements ActionListener {

   public void service (PortletRequest request, PortletResponse response)
      throws PortletException, IOException {

      // Get the portlet state object from session, if not there get the
      // initial state for the mode.
      State nextState = (State)
      session.getAttribute(request.getMode().toString());
      if (nextState == null)
         nextState = InitialStateManager.getInitialState(request.getMode());

      //  Dispatch to state
      nextState.performView(request, response,
         getPortletConfig().getContext());
   }

   public void actionPerformed(ActionEvent event) throws PortletException {

      //  Get the action handler class and dispatch
      String actionClassName = event.getActionString();
      Action action = ActionClassManager.getAction(actionClassName);

      //  Dispatch to that class event handler
      action.actionPerformed(request, portletContext);
      action.setState(request);
   }
}

The ActionClassManager is responsible to return an Action subclass instance given a class identifier that was added to the PortletURI using the addAction (String) method. We follow a convention of having that class identifier be the fully qualified class name so we could easily create a class instance. However, we could avoid unnecessary object creations by handling the Action subclass instances as singletons. Then our ActionClassManager just needs to return the singleton instance for the referenced Action subclass.

Listing 2. ActionClassManager Class

public class ActionClassManager {
    //  Map of action class instances
    static HashMap actionInstances = new HashMap();

   /**
     * Return the Action class instance for the named class.
     * @param    The action class name
     * @return    The Action instance
     * @exception    AIMException - Wraps ClassNotFoundException,
     *       IllegalAccessException, or InstantiationException
     */
     public static Action getAction(String actionClassName)
       throws AIMException {

     Action action = null;
     try {
       action = (Action)actionInstances.get(actionClassName);
       if (action == null) {
         action = (Action) Class.forName(actionClassName).newInstance();
         actionInstances.put(actionClassName, action);
       }
     } catch (ClassNotFoundException e) {
       throw new AIMWrapperException(e.getLocalizedMessage(), e);
     } catch (IllegalAccessException e) {
       throw new AIMWrapperException(e.getLocalizedMessage(), e);
     } catch (InstantiationException e) {
       throw new AIMWrapperException(e.getLocalizedMessage(), e);
     }
     return action;
     }

}

The abstract Action class defines two abstract methods that the subclass must implement the actionPerformed method and the setState method. The Action subclass's actionPerformed method gets invoked when the StateManagerPortlet class dispatches processing from its actionPerformed method.

The setState method is also invoked from the StateManagerPortlet and ensures that the action subclass implementation sets the next state after action processing completes for this user request. The Acton class implements its own setState method that the subclasses should call to actually set the next state. This ensures that the Action and State classes know where to set and retrieve the next state, without the subclasses needing to know those mechanics. This State spans HTTP requests and are maintained per portlet mode. So, when the user changes modes control returns to the last state that was visited.

Listing 3. Action class

public abstract class Action implements PortletAction {

   public abstract void actionPerformed(PortletRequest request,
      PortletContext portletContext) throws AIMException;

   public abstract void setState(PortletRequest request)
         throws AIMException;

   public void setState(PortletRequest request, State nextState)
         throws AIMException {

      PortletSession session = request.getPortletSession();
      session.setAttribute(request.getMode().toString(), nextState);

    }
}

Our MainViewAction class does not require any specific action processing. Actually, we know that we need to get the list of contacts to present in the main view. We can implement the code that creates the appropriate list of Contacts beans in our actionPerformed method, set it in session or on the request object, then set the next state to our MainViewState. That class's performViewimplementation can simply invoke the JSP and render the list contents from the bean in the session or on the request. However, we have to be concerned about portal page refreshes. In the case of a refresh our portlet's MainViewState performViewmethod will get called, but not our MainViewAction actionPerformed method. So, we can't just have the bean on the request object without having the performView method recreate the bean and put the new bean on the request object. We can reuse the bean on session. But, in that case our data will get stale. If we make a change in edit mode then we will have to signal that change and refresh our data bean in the State method. To simplify the processing, we will just get the data beans in the State method for our MainViewState.

So, our actionPerformed method in the MainViewAction class does no additional processing. That class simply sets the MainViewState. The relevant code is shown below.

Listing 4. MainViewAction class

public class MainViewAction extends Action {
   /**
    *  Perform action request
    */

   public void actionPerformed(PortletRequest request,
      PortletContext portletContext) throws AIMException {
   }

   /**
    *  Set the next Portlet state
    */
   public void setState(PortletRequest request)
         throws AIMException {
      setState(request, new MainViewState());
   }
}

When the actionPerformed finishes processing, control returns to the portal container for other listener notification handlers to execute. The portal container then continues portlet request processing by calling the service method of the StateManagerPortlet.

The StateManagerPortlet service method first attempts to retrieve the reference to the State class that was set by the previously executing Action class. If there is no State class reference found (initial portlet invocation, for example) then a helper class, InitialStateManager, is used to determine the initial State class for each portlet mode. InitialStateManager has a single method called getInitialState that returns an instance of a state object based on the current mode of the portlet. For our contacts list portlet, this class would return an instance of MainViewState for the view mode.

The State interface has a single method, the performViewmethod, that all of the state classes need to implement. This method gets invoked by the StateManagerPortlet's service method.

Listing 5. InitialStateManager class

public class InitialStateManager {

   public static State getInitialState(Portlet.Mode mode)
      throws AIMException {

      //  Return the initial VIEW State
      if (mode == Portlet.Mode.VIEW)
         return new MainViewState();

      //  Return the initial EDIT State
      if (mode == Portlet.Mode.EDIT)
         return new MainEditState();

      //  Return the initial HELP State
      if (mode == Portlet.Mode.HELP)
         return null;

      //  Return the initial CONFIGURE State
      if (mode == Portlet.Mode.CONFIGURE)
         return new ConfigureState();

      String msg = ResourceBundle.getBundle("nls.polling").
         getString("exception.initial");
      throw new AIMException (msg);
   }
}

So, our MainViewState performViewmethod is responsible to get the list of contacts in the form of a list of Contacts beans. The contact list is persisted in a database. The list of contacts is then passed to the JSP for rendering in the main view.

Listing 6. MainViewState class

public class MainViewState implements State {

   //  Static fields
   protected static MainViewState stateInstance = null;

   /**
     * Contact list view
     */
   public void performView(PortletRequest request,
      PortletResponse response,
      PortletContext portletContext)
      throws IOException, PortletException, AIMException {

      //  Get the logged in user
      String user = request.getUser().getUserID();

      // Get the ContactList data, put it on the request object
      String datasource = portletSettings.getAttribute(DATASOURCE);
      ContactListBroker broker = new ContactListBroker(datasource);
      List contactList = broker.getContactList(user);
      request.setAttribute(CONTACT_LIST, contactList);

      // Invoke the JSP to render
      portletContext.include(VIEW_LIST_JSP, request, response);

   }

   /**
    *  Return the singleton for this class
    */
   public static State getInstance() {
      if (stateInstance == null)
         stateInstance = new MainViewState();
      return stateInstance;
   }

}

The following shows the JSP code used to render the main list view. Here, notice the HREF on the anchor tag. This anchor tag lets the user click on a contact entry, and then displays the detail view for the contact.

The HREF for this tag uses the createURI tag from the portlet tag library. That tag takes a parameter in URIAction, which we set to the name of the Action class method that will handle the event processing for that user click action.

Listing 7. Main List View JSP


<%@ page contentType="text/html"%>
<%@ page import="com.ibm.sample.contacts.beans.Contact" %>
<%@ taglib uri="/WEB-INF/tld/portlet.tld" prefix="portletAPI" %>
<portletAPI:init />

<jsp:useBean id="contactList" class="java.util.List" scope="request" />

<%
   String WHITE = "#ffffff";
   String GRAY = "#efefef";
   String bgColorStr = WHITE;
   String detailViewAction =
      "com.ibm.sample.contacts.actions.DetailViewAction";
%>

   <form method="POST" name="<portletAPI:encodeNamespace value="form"/>">
   <input type="hidden" name="selectedContact">
   <table border="0" width="85%" align="center"
      cellspacing="0" cellpadding="0">
   <thead>
   <tr><td><br></td></tr>
   <tr>
      <td class="wpsTableHead">Name</td>
      <td class="wpsTableHead">Company</td>
      <td class="wpsTableHead">Email</td>
      <td class="wpsTableHead">Phone</td>
   </tr>
   </thead>
   <tbody>
   <%
      for (int _ind=0; _ind<contactList.size(); _ind++ )  {
         Contact contact = (Contact)contactList.get(_ind);
         // alternate between white and gray rows in table
         if ( (_ind & 0x1) == 0x1 ) {bgColorStr = GRAY; }
         else {bgColorStr = WHITE; }
    %>
   <tr>
      <td class="wpsPortletSmText" valign="top" align="left"
         bgcolor="<%= bgColorStr %>">

      <a style="text-decoration: none; color:black;"
         onMouseOver="this.style.color='red'"
         onMouseOut="this.style.color='#000000'"
    href="javascript:document.<portletAPI:encodeNamespace
   value="form"/>.action=
   '<portletAPI:createURI>
   <portletAPI:URIAction name='<%=detailViewAction%>'/>
   </portletAPI:createURI>';
   document.<portletAPI:encodeNamespace
   value="form"/>.selectedContact.value='<%=contact.getOid()%>';
   document.<portletAPI:encodeNamespace
   value="form"/>.submit()">
         <%= contact.getFirstName() %>
         <%= contact.getLastName() %>
         </a>
         </td>
      <td class="wpsPortletSmText" valign="top" align="left"
         bgcolor="<%= bgColorStr %>">
         <%= contact.getCompany() %>
      </td>
      <td class="wpsPortletSmText" valign="top" align="left"
         bgcolor="<%= bgColorStr %>">
         <%= contact.getEmail() %>
      </td>
      <td class="wpsPortletSmText" valign="top" align="left"
         bgcolor="<%= bgColorStr %>">
         <%= contact.getMobilePhone() %>
      </td>
   </tr>
   <%
      }
   %>
  </tbody>
  </table>
  </form>


Figure 4. Main list view
Main list view

From this main view page the user can select a specific contact entry to get detailed information. We have added an anchor tag that displays the first name and last name of the contact entry as a clickable link. When clicked, the StateManagerPortlet's actionPerformed method gets called with the DetailViewAction class name as the Action name parameter.

The actionPerformed method gets the object id of the selected contact entry and calls the persistence class broker to get the instantiated Contact object for that id. This Contact object bean is put on session for the State class to use to render the detail view. If that page gets refreshed by virtue of a portal page refresh the bean will be available from session and we do not need to be concerned about stale data since this object will only get updated through this user's use of the portlet.

Another key on a portal page refresh is that the form data does not get reposted. Therefore, if we attempt to get the object id of the selected Contact as a request parameter in our State class it will not be available on a portal page refresh. So, for the portlet to behave properly, the form data parameter must be retrieved once initially and then stored where it can be referenced on subsequent page refreshes. Since the actionPerformed method gets invoked once this is a good place for that code.

Keeping the back-end data access in the actionPerformed method ensures that we don't have multiple database accesses for the same data during page refreshes. Of course, when and how often you should go back to the source for a data refresh depends on the portlet requirements and the nature of the data. In this case, the data is not dynamic and should be kept cached while the portlet page is being refreshed.

Finally, the DetailViewState is set as the next state for this portlet where processing will continue for our portlet.

Listing 8. DetailViewAction class


public class DetailViewAction extends Action {

   /**
    *  Perform action request
    */
   public void actionPerformed(PortletRequest request,
      PortletContext portletContext)
      throws AIMException {

      //  Get the userid and the selected contact oid
      String userid = request.getUser().getUserID();
      String oid = request.getParameter(SELECTED_CONTACT);

      //  Get the Contact element and put it on session
      PortletSettings portletSettings = request.getPortletSettings();
      String datasource = portletSettings.getAttribute(DATASOURCE);
      ContactListBroker broker = new ContactListBroker(datasource);
      Contact contact = broker.getContact(userid, oid);
      PortletSession session = request.getPortletSession();
      session.setAttribute(CONTACT, contact);

   }

   /**
    *  Set the next Portlet state... if this is a MODE change action
    *  (createReturnURI) then set the next state to null, to allow
    *  the default state in the next mode (since we do not know
    *  the mode we are returning to).
    */
   public void setState(PortletRequest request)
      throws AIMException {

      setState(request, DetailViewState.getInstance());

   }

}

The DetailViewState simply invokes the JSP to render the detail view. The JSP gets the contact bean from the session. A portion of the JSP for this view is shown below. In our UI interaction, when the user clicks the OK button the processing continues by going back to the main view. The MainViewAction actionPerformed method simply cleans up the session data by removing the contact object id that we set in the DetailViewAction class.

Listing 9. Contact Detail View JSP

<%@ page contentType="text/html"%>
<%@ taglib uri="/WEB-INF/tld/portlet.tld" prefix="portletAPI" %>
<portletAPI:init />

<jsp:useBean id="contact" class="com.ibm.sample.contacts.beans.Contact"
   scope="session" />
<%
   String viewAction = "com.ibm.sample.contacts.actions.MainViewAction";
%>

<table align="left" border='0' width=400>
   <form method="post" name="<portletAPI:encodeNamespace value="form"/>">

   <tr>
     <td class="wpsInlineHelpText" colspan="2">
        <img src='<%=
        response.encodeURL("/images/msg_inline_help.gif")%>'
          border="0"/> 
        Contact Detail Information
      </td>
   </tr>
   <tr><td colspan="2"><br/></td></tr>

   <tr>
      <td class="wpsTableHead">
         First Name
      </td>
      <td class="wpsPortletSmText">
         <%= contact.getFirstName() %>
      </td>
   </tr>

   <tr>
      <td class="wpsTableHead">
         Last Name
      </td>
      <td class="wpsPortletSmText">
         <%= contact.getLastName() %>
      </td>
   </tr>

   <tr>
      <td class="wpsTableHead">
       Email
      </td>
      <td class="wpsPortletSmText">
         <%= contact.getEmail() %>
      </td>
   </tr>


   ...  More here ...


   <tr><td colspan="2"><br/></td></tr>
   <tr>
     <td>
       <input class="wpsButtonText"
        type="submit"
        value='Ok;'
        onClick="document.<portletAPI:encodeNamespace
           value="form"/>.action=
     '<portletAPI:createReturnURI><portletAPI:URIAction
      name='<%=viewAction%>'/>
     </portletAPI:createReturnURI>'">
         </td>
      </tr>
   </form>
</table>



Figure 5. Detail view
Detail view

This completes the UI interaction flow for the contact list portlet's view mode. As you can see, the flow logic proceeds in a well-organized manner and you do not have tedious control code in the portlet. The edit and configure modes of the portlet would be implemented using the same approach. The main edit view is shown here.


Figure 6. Edit view
Edit view

Completing the portlet implementation

The remaining part of the application follows this same pattern. The process control logic flows exactly the same way for the edit mode as it does for the View mode. We also implement a configuration mode that allows the user to specify the data source that will be used to persist the contact data. It will be implemented in this same manner. The configuration mode has only one view that allows the user to enter the data source. There are two additional Action classes that will be used to verify the data source and to save the data source as configuration data.

The following are the basic steps for the implementation of the state pattern.

  1. The initial State class for the specific mode being executed is provided by the InitialStateManager class.
  2. That State's performViewmethod is invoked, having been dispatched to by the StateManagerPortlet, does any necessary application logic processing and then invokes its JSP.
  3. The JSP uses the PortletURI tag to generate the appropriate HREF for clickable actions on the UI. Associated with each user action, through a parameter on the PortletURI, is the name of the Action subclass where event processing should occur. Each user action would typically specify a different Action subclass and that subclass would implement a single, specific function.
  4. When the user clicks on a link, the Action class's actionPerformed method is invoked (again by virtue of being dispatched to by the StateManagerPortlet). The action logic is performed and the appropriate State class is set for processing to continue.
  5. The StateManagerPortlet service method gets called when event processing completes and again dispatches to the State class that was set in the Action event handling phase. The State class performs application logic and calls its JSP to render the results.
  6. Processing continues in this manner as the user navigates through the portlet.

Persistence broker

Additional portlet components are required to access the persistent data in the database. An AbstractBroker class provides generic JDBC database access function. It can be used to get the database DataSource and Connection. It maintains the DataSource in cache to avoid repeated, costly jndi lookups. It also provides common code to execute a PreparedStatement and close the Connection, Statement and ResultSet.

A ContactListBroker class extends AbstractBroker. It implements data access methods that are specific to the portlet requirements, such as a getContact method and a getContactList method. Methods are also available to save and delete a contact entry. It also provides function to verify the table schema so we can verify the data source specified by the user in configuration mode.

Exception handling

We have implemented a number of exception classes for our portlet. The base class is called AIMException. An AIMWrapperException is a subclass of AIMException. We can use AIMWrapperException to wrap other thrown exceptions in order to modify display behavior and manage exception handling efficiently in the StateManagerPortlet service method.

The AIMMessageException is an exception that we use to generate an informational or error message to the portlet UI while terminating processing. For example, we can throw an AIMMessage exception with a message indicating that the user must first log in.

Exceptions thrown in the actionPerformed methods or setState methods are caught and deferred to the service method. The deferral is managed by catching any thrown exceptions in these methods and putting the exception (usually wrapped in an AIMWrapperException) on the request object. When the StateManagerPortlet service method is called, it first checks for any deferred exceptions and if any are found, they are re-thrown and handled from there.

All portlet application exceptions are therefore handled in the StateManagerPortlet service method. If exceptions are found, a display exception method is called where either the exception message will be displayed in the portlet or the exception message and the associated stacktrace will be displayed. Putting the stack trace out to the portlet helps debugging during development. Since you would not want this level of detail to be displayed to the user in production it is configurable. This is the debugTrace parameter defined in the portlet deployment descriptor


Implementation class summary
PckageClassDescription
com.ibm.sample.contacts.actions ActionAbstract Action class
MainViewActionHandles request to display the main view (list of contacts)
DetailViewActionHandles request to display the detail view of a contact
MainEditActionHandles request to display the main edit view
AddViewActionHandles request to display the add contact view
AddActionHandles request to actually add a contact
ModifyViewActionHandles request to display the modify contact view
ModifyActionHandles request to actually save a modified contact
DeleteActionHandles request to actually delete a contact
ConfigureActionHandles request display the data source configuration view
SaveConfigActionHandles request to save the changed data source configuration setting
VerifyConfigActionHandles request to verify the data source configuration setting
com.ibm.sample.contacts.states StateBase State Interface
MainViewStateRender the main contact list view
DetailViewStateRender the detail contact view
MainEditStateRender the main edit view
AddViewStateRender the add contact view
ModifyViewStateRender the modify contact view
ConfigureStateRender the configuration parameter settings view
com.ibm.sample.contacts.portlet StateManagerPortletPortlet class
ActionClassManagerReturn an Action class instance given a name reference
ConstantsDefines the constants used in the portlet
InitialStateManagerHandles requests for initial portlet States
com.ibm.sample.contacts.persistence AbstractBrokerBase broker class
ContactListBrokerPortlet specific broker function
com.ibm.sample.contacts.beans ContactRepresents a contact entry object
ContactHelperProvides common function to add, delete, and modify a contact entry
com.ibm.sample.contacts.utilities AIMExceptionBase exception class
AIMWrapperExceptionWraps another exception
AIMMessageExceptionHandles process interrupting messages to the UI

Conclusion

Portlet development guidelines and example implementations provide a good understanding of the portlet API. However, implementing complex process control is outside the scope of the API. Without a well-designed approach to this problem of how to best implement control logic you end up creating portlets that contain a fair amount of code dedicated to simply addressing the intent of a user's request.

Besides being repetitive, this code makes the portlet much harder to read since you need to wade through the control logic to get to the real work that the portlet is doing. The control logic is also prone to error because it relies on various aspects, such as string name matching to hook an event to a listener or a listener action to behavior in the portlet method. To solve these problems with control logic, you can consider your application to be a collection of portlet actions and states. Then, given a well-defined approach to state transitions, you can remove the cumbersome control logic code from within the portlet application.

Developers may also choose to implement their portlets using the struts framework. Struts is a Jakarta project providing a very popular open source framework for building web applications. Using the Struts Portlet Framework provided with Portal, portlets written using the struts framework can be run in WebSphere Portal. Struts provides considerable web application functionality beyond page navigation and is an excellent choice for portlet development. Developers not familiar with struts or who wish to have more direct access to the portlet API will benefit from a state transition implementation for page navigation such as the light-weight pattern described here. This pattern, as well as the struts framework, promotes an application architecture based on the Model-2 Model-View-Controller design.

This article discussed an implementation of a state pattern for portlet development that provides the ability to manage complex application page flow within a portlet. The state pattern provides efficient processing and clean code that helps developers easily debug, maintain, and enhance an application. The complete implementation for this sample is provided in the download ZIP file below.



Download

NameSizeDownload method
ContactList.zip75KBFTP|HTTP

Information about download methods


Resources

About the author

Tim Hanis is a senior software engineer for WebSphere Portal at IBM Research Triangle Park Lab in Raleigh, North Carolina.

Report abuse help

Report abuse

Thank you. This entry has been flagged for moderator attention.


Report abuse help

Report abuse

Report abuse submission failed. Please try again later.


developerWorks: Sign in


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. Select information in your profile (name, country/region, and company) is displayed to the public and will accompany any content you post. You may update your IBM account at any time.

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.

(Must be between 3 – 31 characters.)

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

 


Rate this article

Comments

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere
ArticleID=14272
ArticleTitle=Applying the State Pattern to WebSphere Portal V5 Portlets: Part 2. Implementation
publish-date=12032003
author1-email=hanistt@us.ibm.com
author1-email-cc=