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 Portlets: Part 2 -- Implementation

Tim Hanis (mailto:hanistt@us.ibm.com), Senior Consultant, IBM Raleigh Lab
Tim Hanis is a senior consultant for IBM WebSphere Portal at IBM Research Triangle Park Lab in Raleigh, North Carolina. You can reach Tim at hanistt@us.ibm.com.

Summary:  This is Part 2 in a series that discusses how to apply the state pattern to your portlets so that you end up with a cleaner portlet implementation. Part 2 demonstrates how to implement a portlet by applying the state pattern.

Date:  11 Dec 2002
Level:  Intermediate

Activity:  3362 views
Comments:  

© Copyright International Business Machines Corporation 2002. All rights reserved.

Introduction

Part 1 presented an overview of using the state pattern when developing your portlets for IBM ® WebSphere® Portal. Part 2 presents a specific implementation of an sample portlet that uses the state pattern. The sample portlet that maintains a list of contacts for a user. The contact list is persisted in a database and the portlet lets the user view the contacts individually or in a list. The developer can add, delete, or modify the contacts when in edit mode. In configuration mode, the developer can set database access information.

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 string name matching to hook an event to a listener or a listener action to behavior in the portlet method. It can also complicate the business logic since the implementation of the control function can resemble the business logic.

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. This series discusses how you can apply this same approach to state management for portlet development, and create a reusable state pattern to address this problem generically. You will find that applying the state pattern to your portlet results in a cleaner portlet implementation.

In Part 2, you will implement the fully functional portlet, focusing specifically on the state pattern implementation. This articles discusses how to use WebSphere Studio Application Developer (hereafter called Application Developer) to create the portlet. However, you can also create the portlet without using the Application Developer development environment. Other portlet requirements, such as database access, exception handling, and configuration parameters are mentioned briefly.

The following are prerequisites for the portlet code:

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


A quick review of the state pattern

The StateManagerPortlet is the main portlet class; this is where you typically write all of the portlet-specific code. However, in this implementation, the class is portlet-independent. It serves as a dispatcher to support action and state classes where the portlet code resides. Classes that implement the action interface will implement an actionPerformed method. This method has the responsibility to perform any action necessary to implement the function required by the action request. Classes that implement the state interface will implement a perform method. This method gets called from the doView, doEdit, doHelp, or doConfigure methods of the StateManagerPortlet, and contains the code that would normally reside in these methods. Again, this code is specific to the state class where it resides, and therefore avoids all of the additional control-logic clutter. Finally, the StateURI provides a custom tag for the JSP to use. The JSP will associate a link on the HTML page with an action class instance.

Applying the state pattern results in a cleaner implementation. Also, as you move between portlet modes, the state is always remembered. With this pattern, you can easily determine where and when you want data to be retrieved from the source, and when it should retrieved from cache. Since actionPerformed methods do not get invoked on portal page refreshes, you can place data access code in the action state and cache them there. The state classes may use data from cache to avoid multiple trips to get data when the portal page is refreshed.


The contacts list portlet

As mentioned in the introduction, you will work with an implementation of a portlet that maintains a list of contacts (a simple address book) for a user. The portlet lets the user view the contact entries individually or in a list. Again, the developer can add, delete, or modify the contacts when in edit mode. In configuration mode, the developer can set database access information.

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 with the existing data inserted for modification

In this scenario, you do not have an explicit page to delete an entry. The behavior will is such that the user can select a contact entry to delete from the main edit page. There will be no confirmation page or successful execution page. Entry deletion processing occurs and the main edit page is refreshed. If an error occurs, an appropriate message will be displayed.

Assume that you want to have the Main 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 for the portlet. In this case, the Main and Detail view pages will be controlled from the doView portlet method. The remaining pages will be available with the portlet when in Edit mode.

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 command line (substitute the schema name of your choice, but the table name is fixed). You can either create a new database (dataspace) for this table or add it to an existing one. If using DB2, copy the following command to the Command Center, and then run it from the Command Center after establishing a connection to the appropriate database:

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

Now, you can add an entry into the contacts list table so that you can test your portlet more easily as you are developing it. You can execute the following SQL command to create a new entry (if you are going to log in to the portal using 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-247-4098') 

Next, create a datasource in WebSphere Application Server for the database where you created the Contacts table. In the Application Server Administrative Console, select Resources => JDBC Providers, and then expand the appropriate JDBC Driver. Right-click on Data Sources, and select New. Enter the datasource name for the Name value. Enter the database name, userID, and password. Then, test the connection.


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

Setting up the development environment

You will use WebSphere Studio Application Developer to create the portlet. In Application Developer, the following JAR files must be available on the build path in order for the portlet code to compile successfully. 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.

In Application Developer, create a Web project. If you are using the Portlet plug-in for Application Developer that is provided with WebSphere Portal, then you can create a portlet application project instead. Creating a portlet application project using the portlet plug-in will help set up the build classpath. In either case (or if using a different development enviornment), ensure that the following JAR files are on the build classpath:

  • WAS_PLUGINDIR/lib/j2ee.jar
  • WAS_PLUGINDIR/lib/webcontainer.jar
  • WAS_PLUGINDIR/lib/ivjejb35.jar
  • WAS_PLUGINDIR/lib/websphere.jar
  • WAS_PLUGINDIR//lib/cm.jar
  • WPS_PLUGINDIR/portlet-api.jar
  • WPS_PLUGINDIR/wpsportlets.jar
  • WPS_PLUGINDIR/wps.jar

If you are using Application Developer with the Portal plug-in installed, then the JAR files are available in:

  • WAS_PLUGINDIR = <wsad_root>\plugins\com.ibm.etools.websphere.runtime\lib
  • WPS_PLUGINDIR = <wsad_root>\plugins\com.ibm.wps

Otherwise, you can find the files in <was_root>\lib\ and <wps_root>\lib\app.


Starting the implementation

Consider the states and actions that make up this portlet. Again, think of an action as some user-level 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. A state is the condition of the portlet after the user action has been completed and usually has a visual component. For example, the action of adding a new contact might result in the state of the portlet in edit mode, displaying the main list of contacts.

Now, think of how you want the portlet look visually. In this example, you want a main view page that lists the contacts you have created. The page should let users select one of the contacts and get additional detail about this person.

You should also be able to add, delete, and modify contacts from the list. You need a main edit page that also lists the contacts, but this time lets you delete or modify a selection. To delete, you don't need to implement another view; if you delete a selection, you simply stay on the same main edit page with the contact list properly refreshed. If you select a contact to modify, you will be taken to a detail page where you can update any of the attributes of this contact entry. When you have completed the modifications, you will be returned to the main edit page. Similarly, from the main edit page, you can add a new contact. You will be taken to another detail page where you can enter and save the attributes of a contact entry. Again, after saving, you will be taken back to the main edit page.

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

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 2. State transitions available for this application
Diagram of the state transitions available for the application

Consider how everything works together. To get started, take a look at only a piece of the portlet. Review the actions and states that impact the ability to view the main contact list, and then select a specific entry and view its details.

First, you need to understand what the main portlet class does. This class, StateManagerPortlet, 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 the current processing to either the appropriate action class or state class. As you can see, the action classes are called from the actionPerformed method so they get invoked as a result of a user action, for example, clicking on a link. The state class performView method gets invoked when the portlet class service method getting called.

Keep in mind that that this logic flow starts from a user action on the 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 URI. 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 added to the PortletURI. Then, when the user clicks on the desired contact, the StateManagerPortlet class can get the action class from the event object (using the standard getAction API), and dispatch to its actionPerformed method.

The code below is the 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 that 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 { 
   
     // Execute the perform action method for the event   
     PortletRequest request = event.getRequest();   
     Action action = (Action) event.getAction();   
     PortletContext portletContext = getPortletConfig().getContext();   
     action.actionPerformed(request, portletContext);   
     action.setState(request);  
  
   }  
 
}

So, what does the action class do for this function? The action class must first implement the interface PortletAction so that you can add it to the PortletURI. You also need to implement some common functions for the action classes; the abstract action class implements PortletAction. The action classes will extend the abstract action class. The abstract action class is provided below. It has a clean interface with two methods defined for the subclasses to implement. The actionPerformed method is the method that gets invoked when the StateManagerPortlet class dispatches. The setState method is also invoked from the StateManagerPortlet and ensures that the action class implementation sets the state that is the result of the transition with this action.

The abstract class also has a setState method that the action classes can call to handle the setting of the state class, so that the StateManagerPortlet knows where to get it. The abstract action class also has a convenience method to get the PortletLog for trace and message logging.

Listing 2. 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);   
    }   
  
   public PortletLog getLog () {  
      return PortletLogImpl.getInstance();   
   }   
} 
 

In this example implementation, you have a MainViewAction class that extends the abstract action class, and implements the actionPerformed and the setState methods. For MainViewAction, no application work is required in the actionPerformed method, and consequently, the implementation of the method is empty. The setState method sets MainViewState as the state that you transition to (using the abstract action class' setState method).

Listing 3. 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 container then continues portlet request processing by calling the service method of the StateManagerPortlet.

As in the StateManagerPortlet code, the service method attempts to retrieve the state class that was set by the previous action class. A state might not be set if this was the first invocation of the portlet; no action event would have been invoked, so the actionPerformed method would not have been called. In this case, you get the initial state from the helper class, InitialStateManager. InitialStateManager has a single method called getInitialState that returns an instance of a state object based on the current mode of the portlet. In the example, for an initial portlet invocation, the InitialStateManager would return an instance of MainViewState for the default View mode of the portlet.

You could have easily hardcoded these initial values in the StateManagerPortlet class, but this abstraction lets the StateManagerPortlet class be generic and lets the implementer choose if these values are read dynamically or hardcoded in.

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

Listing 4. State interface

  
public interface State {   
  
   public void performView(PortletRequest request,   
      PortletResponse response, PortletContext portletContext)   
         throws IOException, PortletException, AIMException;   
}

Then, the MainViewState class implements the performView method. It is responsible for getting the list of contacts from the database broker as a collection of model objects called Contacts. It then adds this collection to the request object for the JSP to render the page. The JSP is invoked and the page is rendered.

Listing 5. MainViewState class

  
public class MainViewState implements State {   
  
   //  Static fields   
   protected static final String viewListJsp = "/jsp/contacts/viewList.jsp";  
  
   /**   
     * Contact list view   
    */   
   public void performView(PortletRequest request, PortletResponse response,  
      PortletContext portletContext)    
         throws IOException, PortletException, AIMException {   
  
      // Get the ContactList data, put it on the request object   
      List contactList = ContactListBroker.getInstance().getContactList();  
      request.setAttribute("contactList", contactList);   
  
      // Invoke the JSP to render   
      portletContext.include(viewListJsp, request, response);
  
    }  
 
}

The following table shows most of 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 following custom tag:

<stateURI:createURI state="com.ibm.aim.actions.DetailViewAction"/>

This tag creates a URI with an instance of the specific action class that was added using the PortletURI addAction method. Now, you have a URI referencing back to the portlet for which you can get the proper transition to the appropriate action, and then have the action class provide the appropriate transition to the proper state class. If more actions were available to the user in this view, you could create the appropriate portletURIs with the correct action classes, so that correct processing would continue in the state transition. The code for this custom tag is provided in Listing 10.

Listing 6. Main List View JSP

 
<jsp:useBean id="contactList" class="java.util.List" scope="request" />   
<%@ page import="com.ibm.aim.beans.Contact" %>  
  
  <form method="POST" name="<portletAPI:encodeNamespace value="viewList"/>" >   
  <input type="hidden" name="selectedContact">   
  <table border="0" width="85%" align="center" cellspacing="0" cellpadding="0" > 
   <thead>   
    <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);  
   %>  
       <tr>  
          <td class="wpsPortletSmText" valign="top" align="left">  
               <a style="text-decoration: none; color:black;  
                     font-family: sans-serif; font-size: 8pt;"   
                     onMouseOver="this.style.color='red'"  
                     onMouseOut="this.style.color='#000000'"  
                     href="javascript:document.<portletAPI:encodeNamespace  
                     value="viewList"/>.action='<stateURI:createURI  
                     state="com.ibm.aim.actions.DetailViewAction"/>';  
                     document.<portletAPI:encodeNamespace  
                     value="viewList"/>.selectedContact.value= 
                     '<%=contact.getOid()%>';  
                     document.<portletAPI:encodeNamespace  
                     value="viewList"/>.submit()">  
 
                 <%= contact.getFirstName() %>  
                 <%= contact.getLastName() %>  
              </a>  
          </td>  
          <td class="wpsPortletSmText" valign="top" align="left">  
               <%= contact.getCompany() %>  
          </td>  
          <td class="wpsPortletSmText" valign="top" align="left">  
              <%= contact.getEmail() %>  
          </td>  
          <td class="wpsPortleSmText" valign="top" align="left">  
              <%= contact.getMobilePhone() %>  
          </td>  
       </tr>  
   <%   
     }  
   %>  
   </tbody>  
  </table>  
 </form>


Figure 3. Main List View

Continuing with the example, the user selects a specific Contact entry to get detailed information. The StateManagerPortlet actionPerformed method gets called. It gets the DetailViewAction class from the event object, and invokes its actionPeformed method and then its setState method. The actionPerformed method gets the userID of the selected Contact, calls the broker to get the instantiated contact object for the userID, and puts the model object on session for the State class to use.

Why do this work here and put it on session, instead of just getting the OID and the object in the next state class directly?

The key is that on a page refresh for the portal, two things happen: the form data does not get reposted and the portlet's actionPeformed method does not get invoked (but the portlet's service method will).

Therefore, if you attempt to get the userID of the selected Contact as a request object parameter, then you will not find it on a page refresh. So, for the portlet to behave properly, the data element must be retrieved once initially, and then stored someplace so that it can be referenced on subsequent page refreshes. Since the actionPerformed method gets invoked once, then this is a good place to place this code.

Also, since the actionPerformed method does not get invoked on subsequent page refreshes, you can place the code that accesses the source data from the database there, so that you do not attempt to retrieve the contact information each time the portal page is refreshed. It is preferable to do this once, and keep it available for subsequent 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:

Listing 7. DetailViewAction class

  
public class DetailViewAction extends Action {   
  
   /**   
    *  Perform action request   
    */   
    public void actionPerformed(PortletRequest request,   
       PortletContext portletContext)   
           throws AIMException {    
  
       //  Get the userid   
       String userid = request.getUser().getUserID();   
  
       //  Get the selected contact oid  
       String oid = request.getParameter("selectedContact");   
  
       //  Get the Contact element and put it on session   
       Contact contact =   
          ContactListBroker.getInstance().getContact(userid, oid);   
       PortletSession session = request.getPortletSession();   
       session.setAttribute("contact", contact);   
  
    }   
  
    /**   
     *  Set the next Portlet state   
     */   
     public void setState(PortletRequest request)  
           throws AIMException {   
        setState(request, new DetailViewState());   
     }   
 
} 
 

The DetailViewState simply gets the contact object from the session and puts it on the request object for the JSP to use (this is for consistency in JSP bean handling).

Listing 8. DetailViewState class

  
public class DetailViewState implements State {   
  
  //  Static fields   
  protected static final String viewDetailJsp =  
      "/jsp/contacts/viewDetail.jsp";   
  
  /**   
   *  Perform request   
   */   
  public void performView(PortletRequest request,   
      PortletResponse response, PortletContext portletContext)    
         throws IOException, PortletException, AIMException {   
  
     //  Get the contact from session and set it on the request object   
     //  (yes, strictly not needed since the jsp has access to the session)  
     PortletSession session = request.getPortletSession();   
     Contact contact = (Contact)session.getAttribute("contact");   
     request.setAttribute("contact", contact);  
  
     // Invoke the JSP to render    
     portletContext.include(viewDetailJsp, request, response);  
  
  }   
}

The complete implementation of MainViewAction is slightly different than what is shown earlier here. It cleans up session data by removing the contact object if it exists on the session.

The JSP for this view is shown below. Notice that it also uses the createURI custom tag:

            <stateURI:createURI state="com.ibm.aim.actions.MainViewAction"/>

In this case, when the user clicks the OK button, the processing continues by going back to the main view.

Listing 9. Contact Detail View JSP

            <jsp:useBean id="contact" class="com.ibm.aim.beans.Contact"   
   scope="request" />   
  
<br>   
<p class="wpsPortletHead">   
 Contact Detail Information   
<Br>  
 <p>   
  
<table  align="center" border=3 width=400>   
  
 <!---  Table Rows   --->   
 <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>  
  
  ..................   More here .......................  
 
</table>   
  
<form method="post" name="<portletAPI:encodeNamespace   
    value="viewContactDetail"/>">   
  <table cellspacing="4" cellpadding="0" border="0"   
     width="85%" align="center">   
    <tr>   
        <td height="1" class="wpsAdminHeadSeparator" colspan="3"></td>   
    </tr>   
    <tr>   
      <td>
 
        <a    
          style="text-decoration: none;"   
          href="javascript:document.<portletAPI:encodeNamespace   
             value="viewContactDetail"/>  
          Action='<stateURI:createURI    
             state="com.ibm.aim.actions.MainViewAction"/>';   
          document.<portletAPI:encodeNamespace  
             value="viewContactDetail"/>Submit()" >   
          <IMG src='<%= wpsBaseURL %>/images/admin/header_ok.gif' 
           align="absmiddle" title="Ok" border="0"/> 
           <span class="wpsTaskIconText">OK</span>  
         </a>   
     </td>   
   </tr>  
 </table>  
 </form>


Figure 4. Detail View

This completes the process flow for the portlet's view portion. As you can see, the flow logic proceeds in a well-organized manner and you haven't introduced tedious control code in your portlet.

The edit and configure modes of the portlet would be implemented using the same approach. The main edit view is shown here.


Figure 5. Edit View

Listing 10. StateURI custom tag class

  
public class StateURI extends TagSupport {   
  
   private String state = "";   
  
   public void setState(String state) {   
      this.state = state;   
   }   
  
   public int doStartTag() throws JspException {  
  
      PortletResponse portletResponse = (PortletResponse) 
          ThreadAttributesManager.getAttribute(Constants.PORTLET_RESPONSE); 
       PortletURI portletURI = portletResponse.createURI();  
       Class anAction = Class.forName(state);  
       Action action = (Action) anAction.newInstance();  
       portletURI.addAction(action);  
  
       pageContext.getOut().print(portletURI.toString());  
       return SKIP_BODY;  
  
     }  
   
    public int doEndTag() throws JspException {  
       return EVAL_PAGE;  
     }  
  
 }


Wrapping up the portlet states and actions

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.

  1. The initial state for the Edit view is provided through the InitialStateManager class.
  2. The State class's performView method is invoked, gets its needed model objects, and passes them to the JSP.
  3. The JSP assigns one or more PortletURIs to actions on the UI, and associates each with a specific action class.
  4. When the user clicks on a link, the action class's actionPerformed method is invoked. The action logic is performed and the appropriate state is set.
  5. Processing continues in this manner as the user navigates through the portlet.

Completing the portlet implementation

Persistence broker

Additional portlet components are required to access the persistent data in the database. This complete implementation has a number of classes to support this data access. An AbstractBroker class provides generic JDBC database access function. This class also obtains the correct datasource, gets the database connection, and executes a prepared statement.

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. The ContactListBroker is managed as a singleton, and the class instance is initialized with an implementation of BrokerParms as a parameter.

BrokerParms is an interface that specifies access methods for values that are needed to initialize the broker. The AbstractBroker needs to know the datasource name and the hostname of the server running the JNDI naming service. The methods getDatasourceName and getContextProviderServer are defined in this interface.

You can choose how to get the values that this interface requires as long as the implementation returns the correct datasource and server hostname. You can hardcode the values into an implementation class or the portlet can dynamically configure the settings. In this article, you implemented a dynamic setting approach.

Portlet settings

To maintain the necessary configuration settings for the portlet, you implemented a configure mode in the portlet in order to modify and save the portlet deployment descriptor (portlet.xml) parameters. The implementation of the configure mode function is similar to the state pattern. You have implemented ConfigureAction and ConfigureState classes that provide the function to display the configuration parameters. It lets the user to update and save values.

You have also implemented a PortletParameters class. This class holds the current portlet configuration parameter values. This class also implements BrokerParms; use this class to initialize the ContactListBroker. Therefore, two of the portlet configuration parameters specify the datasource name and the JNDI server hostname.

You can also specify additional configuration values. You have one additional setting named debugTrace. This Boolean value determines how generated error messages are written back to the portlet. See the next section, Exception handling.

Exception handling

You have implemented a number of exception classes for our portlet. The base class is called AIMException. You can create an instance of AIMException with a message indicating the error condition. AIMWrapperException is a subclass of AIMException. You can initialize this subclass with an error message and an exception as parameters. You can use AIMWrapperException to wrap other thrown exceptions in order to modify display behavior and manage exception handling efficiently at the StateManagerPortlet service method.

AIMMessageException is a special case exception that lets you generate an informational or error message to the portlet while terminating processing. For example, you 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 deferring 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 (a) the exception message will be displayed in the portlet or (b) 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, then this behavior is configurable. This is the debugTrace parameter discussed in the Portlet settings section.

By catching and displaying all of the exceptions in the service method (not just AIMExceptions), you can get more useful debug information in the portlet on JSP errors as well. This helps reduce the number of generic messages, such as, "This portlet is unavailable. If the problem persists, please contact the portal administrator." Instead, you will receive a good indication of the problem in the JSP file.

Implementation class summary

com.ibm.aim.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 to save a changed configuration settings
com.ibm.aim.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.aim.portlet StateManagerPortletPortlet class
PortletInitializerInitialize the portlet instance
PortletParametersHandles requests for portlet configuration parameters settings
InitialStateManagerHandles requests for initial portlet States
com.ibm.aim.persistence AbstractBrokerBase broker class
BrokerParmsInterface for parameters needed to initialize broker
ContactListBrokerPortlet specific broker function
com.ibm.aim.beans ContactRepresents a contact entry object
ContactHelperProvides common function to add, delete, and modify a contact entry
com.ibm.aim.tags StateURICustom JSP tag to generate a PortletURI with appropriate Action class
com.ibm.aim.utilities AIMExceptionBase exception class

Conclusion

WebSphere Portal continues to grow as the preferred platform for enterprise portal applications. It is becoming more critical to have well-defined frameworks and patterns for portlet development.

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

DescriptionNameSizeDownload method
Code samplesContactList.zip68 KBFTP|HTTP

Information about download methods


Resources

About the author

Tim Hanis is a senior consultant for IBM WebSphere Portal at IBM Research Triangle Park Lab in Raleigh, North Carolina. You can reach Tim at hanistt@us.ibm.com.

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=13280
ArticleTitle=Applying the State Pattern to WebSphere Portal Portlets: Part 2 -- Implementation
publish-date=12112002