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:
- WebSphere Application Server Version 5.0 (hereafter called Application Server)
- WebSphere Portal for Multiplatforms Version 5.0 (hereafter called WebSphere Portal)
- A database, such as IBM DB2®, to persist the contact entries
- IBM WebSphere Studio V5.01 with Portal Toolkit V5.0 (optional)
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.
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.
- 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).
- In the Application Server Administrative Console, select Resources =>JDBC Providers.
- 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.
- On the DB2 Legacy CLI-based Type 2 JDBC Driver page, ensure that the classpath
is set to the correct location of the
db2java.zipfile. - 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 ofaim, enterjdbc/aim. The portlet will prepend thejdbc/sub context when attempting to a JNDI name lookup. - If your database requires a userid and password for authentication, specify an authentication alias in Component-managed Authentication Alias.
- If you do not already have an alias defined, you can create one in Security => JAAS Configuration => J2C Authentication Data.
- Be sure to save the configuration changes and test the data source connection.
Figure 1. 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 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
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.
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 3. State transitions available for this application
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
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
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
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.
- The initial State class for the specific mode being executed is provided by the InitialStateManager class.
- That State's
performViewmethod is invoked, having been dispatched to by the StateManagerPortlet, does any necessary application logic processing and then invokes its JSP. - 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.
- When the user clicks on a link, the Action class's
actionPerformedmethod 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. - The StateManagerPortlet
servicemethod 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. - Processing continues in this manner as the user navigates through the portlet.
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.
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
| Pckage | Class | Description |
| com.ibm.sample.contacts.actions | Action | Abstract Action class |
| MainViewAction | Handles request to display the main view (list of contacts) | |
| DetailViewAction | Handles request to display the detail view of a contact | |
| MainEditAction | Handles request to display the main edit view | |
| AddViewAction | Handles request to display the add contact view | |
| AddAction | Handles request to actually add a contact | |
| ModifyViewAction | Handles request to display the modify contact view | |
| ModifyAction | Handles request to actually save a modified contact | |
| DeleteAction | Handles request to actually delete a contact | |
| ConfigureAction | Handles request display the data source configuration view | |
| SaveConfigAction | Handles request to save the changed data source configuration setting | |
| VerifyConfigAction | Handles request to verify the data source configuration setting | |
| com.ibm.sample.contacts.states | State | Base State Interface |
| MainViewState | Render the main contact list view | |
| DetailViewState | Render the detail contact view | |
| MainEditState | Render the main edit view | |
| AddViewState | Render the add contact view | |
| ModifyViewState | Render the modify contact view | |
| ConfigureState | Render the configuration parameter settings view | |
| com.ibm.sample.contacts.portlet | StateManagerPortlet | Portlet class |
| ActionClassManager | Return an Action class instance given a name reference | |
| Constants | Defines the constants used in the portlet | |
| InitialStateManager | Handles requests for initial portlet States | |
| com.ibm.sample.contacts.persistence | AbstractBroker | Base broker class |
| ContactListBroker | Portlet specific broker function | |
| com.ibm.sample.contacts.beans | Contact | Represents a contact entry object |
| ContactHelper | Provides common function to add, delete, and modify a contact entry | |
| com.ibm.sample.contacts.utilities | AIMException | Base exception class |
| AIMWrapperException | Wraps another exception | |
| AIMMessageException | Handles process interrupting messages to the UI |
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.
| Name | Size | Download method |
|---|---|---|
| ContactList.zip | 75KB | FTP |
Information about download methods



