In this article, we will discuss an implementation of a Web application, written first as a servlet-based Struts application, and then deployed as a portlet in IBM WebSphere® Portal. Struts, part of the Apache Jakarta Project, provides a very popular open source framework for building Web applications. WebSphere Portal, Version 4.2, provides the Struts Portlet Framework that supports the deployment of Struts applications as portlets. The Struts Portlet Framework also provides support for portal mode and device support within a Struts applications.
For those that have read the articles on applying the State pattern to WebSphere Portal portlets, this paper will show how the same example application, with common design considerations, can be developed and deployed using the Struts framework. As in these other articles, the application we will develop here maintains a list of contacts (address book entries) specific to the logged-in user. The contact list is persisted in a database (or "persisted" in memory), and allows the contacts to be viewed either in a list, or individually with more detail. An edit capability allows contacts to be added, deleted or modified.
We will first complete the implementation as a Struts application without consideration for the portal environment. The focus of this article, however, is not on building a Struts application. For those not familiar with Web application development using Struts, this paper should provide enough information to provide a basic understanding, but is not intended to be a tutorial on using Struts. This application will be developed using the Jakarta Struts Framework that is incorporated into the Struts Portal Framework which, in WebSphere Portal Version 4.2, is Struts 1.1 Beta 2. After the initial development is complete, we will make modifications to deploy the struts application as a portlet.
The following products are used either in the development or deployment of the application discussed in this article:
- WebSphere Portal Version 4.2
- WebSphere Application Server Version 4
- A Database to persist the Contact entries (optional)
- WebSphere Studio Version 4.03 (optional)
Our example application will simply implement standard Create/Read/Update/Delete (CRUD) operations on a set of persisted data. This example application maintains a list of contacts (a simple address book) for a given user. The application lets the user view the contact entries individually or in a list.
From a visual perspective, an implementation of the contacts list application can have the following views or pages:
| View | Displays |
| Login | user login view |
| Main | list of contacts with an option to select one contact for more information |
| Detail | selected contact's detail information |
| Main Edit | list of contacts with options to add, delete, or modify |
| Add Contact | a form view to enter information for a new contact |
| Modify Contact | a form view showing the existing contact element data and allowing updates |
In this scenario, you do not have an explicit page to delete an entry. The behavior 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.
When designing your application, consider the actions and views that will make up this application. 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 view is the representation of the application after the user action has been completed. For example, the action of adding a new contact might result in the view of the portlet in edit mode, displaying the main list of contacts.
Also, consider how you want the application to look visually. In this example, you may 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 details about this individual.
Since we want the contact list to be unique to each user, we will implement a login function as part of the application. The login authentication will be trivial, and when "successful", we will put a user object in session indicating that the user is logged in. When we move to the portal environment, we can simply use the user object that Portal establishes for us instead of using our own. All we need from the user object is a unique identifier, such as the user ID.
You should also be able to add, delete, and modify contacts from the list. You will need a Main Edit page that also lists contacts, but that also lets you delete or modify a selection. To delete, rather than implementing another view, you can simply stay on the same Main Edit page with the contact list properly refreshed. To modify a contact, display a Detail page where you can update any of the attributes of the selected contact entry. When you have completed the modifications, return to the Main Edit page. Similarly, to a new contact from the Main page, go to a Detail page to enter and save the new contact entry attributes, then return again to the Main Edit page.
You can determine the state transitions available for this application. The transitions are managed by applying the appropriate actions which result in the application generating a specific view. In Figure 1, the ovals represent views (JSPs) and the rectangles represent actions. There are some cancel operation transitions (back to the Main Edit view from Add Contact view and Modify Contact view) that are not shown in this diagram.
Figure 1. Transition diagram

Based on our design, we need to implement eight action classes:
- LoginAction
- MainViewAction
- DetailViewAction
- MainEditAction
- AddContactAction
- DeleteContactAction
- ModifyAction
- ModifyContactAction.
Notice that the Add Contact view is really just a direct forward to this JSP from the Main Edit view. Since there is no business logic needed prior to rendering this view, we will simply invoke the Add Contact JSP directly, configuring it as a global forward instead of as an action.
Setting up the development environment
You can use WebSphere Studio Application Developer to create the Struts application. To do this:
- Create a new Web project in Application Developer. This will set up the build path with all needed JAR files, except for
cm.jar. You can add a variable forcm.jarfrom the project properties dialog.
Figure 2. Create a Web project

- Next, import the
PortalStrutsBlank.warfile (see Downloads) into the project (overwriting theweb.xmlfile that was added when the project was initially created). In a standard WebSphere Portal 4.2 installation, you will find thePortalStrutsBlank.warfile in the<wp_root>\dev\struts\StrutsPortletdirectory. Importing this WAR file will add the Struts JAR files to the project lib directory for both Jakarta Struts 1.1 Beta 2 and IBM's Struts Portlet Framework (although for the first part of this exercise we are interested only in building a servlet-based contacts list application). - A default
application.propertiesfile is created during the import. This file is available in the project source directory under the sub-directory resources. During the build process, this file will be moved from thesource/resourcesdirectory to theWEB-INF/classes/resourcesdirectory. - A new directory is also created during the import of
WEB-INF/src/java/resources/application.properties. This directory structure, starting from/src/java/resources/application.properties, can be removed since it is extraneous here. - The import will also bring in a
portlet.xmlfile that is not needed until we attempt to run this application as a portlet. This file can either be deleted or ignored for the time being. - Remove the
jdbc2_0-stdext.jarfile from the lib directory. Otherwise, a ClassCastException may occur oncom.ibm.ejs.cm.JDBC1PhaseRFbecause of conflicts in this JAR and thej2ee.jar. - Finally, move the tag libs from the
/WEB-INF/directory to a new directory,/WEB-INF/tld/. This is just to let us organize the files more carefully.
This completes the basic setup for developing the Struts application in Application Developer. If you choose not to use Application Developer, just make sure the following JAR files are available on the build path (in addition to the Struts JAR files that were imported). The files can be found in <wp_root>\lib\ and <wp_root>\lib\app.
WAS_PLUGINDIR/lib/j2ee.jarWAS_PLUGINDIR/lib/webcontainer.jarWAS_PLUGINDIR/lib/ivjejb35.jarWAS_PLUGINDIR/lib/websphere.jarWAS_PLUGINDIR/lib/cm.jarSERVERJDK_PLUGINDIR/jre/lib/rt.jar
Before considering the implementation details of the action classes, beans,
forms, or the supporting classes, you should first think about the implementation
of the application configuration files. These include the Web application
deployment descriptor (web.xml), the Struts configuration file (struts-config.xml),
and the Struts form validation xml file (validation.xml).
Web Deployment Descriptor: web.xml
A default web.xml file is provided as part of the Portal Struts framework. This file was created when the PortalStrutsBlank.war file was imported, and sets the Struts tag library descriptors and the
default welcome file.
Application-specific changes need to be made to the deployment descriptor:
- Change
<display-name>to a meaningful name for this application. - Change
<servlet-class>toorg.apache.struts.action.ActionServlet. This will be changed back tocom.ibm.wps.portlets.struts.WpsStrutsPortletwhen this Struts application is moved to the portal environment. - The value
<web-app id="WebApp_1862436328">must contain a unique ID. This default ID will be fine for our first Struts application but, in general, should always be a unique value. - Update the action servlet mapping, setting the values of
<servlet-name>to "action" and<url-pattern>to "do". - There are two init parameters needed in the
web.xmlfile to support the persistence classes that are part of the download:persist_to_dbanddatasource. Settingpersist_to_dbto "false" will trigger the use of an in-memory "persistence" broker. Setting this value to "true" will cause a database broker to be employed. If using the latter, be sure to set the name of the datasource that will be used. This value will be ignored if in-memory persistence is used.
Struts configuration: struts-config.xml
The struts-config.xml file contains the application definitions that the Struts framework uses
for control, navigation and configuration settings.
- The form beans needed must be specified. We will have two form beans: one
for login processing, to hold our user ID and password, and the other for
references to our model object, a contact element. The following code segment
defines the form beans in the
struts-config.xmlfile:
<form-beans>
<form-bean name="logonForm" type="com.ibm.aim.view.LoginForm"/>
<form-bean name="contactForm" type="com.ibm.aim.view.ContactForm"/>
</form-beans>
|
- This xml will need to be modified to support the actions identified in our transition diagram. There are eight actions specified, defined below using the class names in our example implementation. We will also specify appropriate "forward" settings for each action, indicating the JSP used to render the requested view. These rendering states were also specified in our transition diagram:
<action
path="/validatelogin"
type="com.ibm.aim.controller.LoginAction"
name="loginForm"
scope="request"
input="/index.jsp">
</action>
<action
path="/mainview"
type="com.ibm.aim.controller.MainViewAction"
name="contactForm"
scope="request"
validate="false" >
<forward
name="success"
path="/WEB-INF/pages/main_view.jsp"/>
</action>
<action
path="/detailview"
type="com.ibm.aim.controller.DetailViewAction"
name="contactForm"
scope="request"
validate="false" >
<forward
name="detailviewpage"
path="/WEB-INF/pages/detail_view.jsp"/>
</action>
<action
path="/mainedit"
type="com.ibm.aim.controller.MainEditAction">
<forward
name="maineditpage"
path="/WEB-INF/pages/main_edit.jsp"/>
</action>
<action
path="/modify"
type="com.ibm.aim.controller.ModifyAction"
name="contactForm"
scope="request"
validate="false" >
<forward
name="editcontactpage"
path="/WEB-INF/pages/edit_contact.jsp"/>
<forward
name="deletecontact"
path="/deletecontact.do"/>
</action>
<action
path="/modifycontact"
type="com.ibm.aim.controller.ModifyContactAction"
name="contactForm"
scope="request"
validate="false" >
<forward
name="mainedit"
path="/mainedit.do"/>
</action>
<action
path="/deletecontact"
type="com.ibm.aim.controller.DeleteContactAction"
name="contactForm"
scope="request"
validate="false" >
<forward
name="maineditpage"
path="/WEB-INF/pages/main_edit.jsp"/>
</action>
<action
path="/addcontact"
type="com.ibm.aim.controller.AddContactAction"
name="contactForm"
scope="request"
validate="false" >
<forward
name="addcontactpage"
path="/WEB-INF/pages/add_contact.jsp"/>
</action> |
- We will also specify global forwards, our message resource file, and the
validation xml file reference:
- Global forwards associates a logical name to a URI for references to either JSPs or Actions. Using global forwards, we protect our code from changes to the URI as long as the logic reference name remains constant.
- The value of the message-resources parameter references our default message resource bundle.
- The
validation.xmlfile defines the validation requirements for our login view.
<global-forwards>
<forward name="login" path="/index.jsp"/>
<forward name="addcontactpage" path="/WEB-INF/pages/add_contact.jsp"/>
<forward name="mainedit" path="/mainedit.do" />
<forward name="mainmenu" path="/mainmenu.do" />
</global-forwards>
<message-resources parameter="resources.application"/>
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property property="pathnames"
value="/WEB-INF/validator-rules.xml,
/WEB-INF/validation.xml"/>
</plug-in>
|
Struts validation: validation.xml
We will use the input validation provided by Struts to ensure that a user does actually enter a user ID and password when attempting to login to the application. This validation simply ensures that some string has been entered for both values; the actual authentication will occur in a bean that we will write specifically for this purpose. (When the application is moved to a portal environment, portal user information will be used and this application-specific login processing will be removed.)
For our input validation, change validation.xml using the following code.
Validation requires the name of the form, the fields and the validation
requirements:
- form:
logonForm - fields:
userNameandpassword, both required. There will also be a resources properties file that defines the label namesloginForm.usernameandloginForm.password. - validation requirements: user enters a user ID and password on the logon form.
<form-validation>
<formset>
<form name="logonForm">
<field property="userName"
depends="required">
<arg0 key="loginForm.username"/>
</field>
<field property="password"
depends="required">
<arg0 key="loginForm.password"/>
</field>
</form>
</formset>
</form-validation>
|
Next, we will implement the Action classes. We will need an implementation
that extends org.apache.struts.action.Action for each of the actions identified
in our state transition diagram and defined in struts-config.xml.
Listing 1 shows the code for one of these action classes. The DeleteContactAction class implements the function to delete a contact. An abstract class that our Action classes extend has also been created, to hold common code needed for all our Action class implementations. For example, we can add code there to verify that the user is logged in before executing this function.
Listing 1. DeleteContactAction Class
class DeleteContactAction extends PostLoginAbstractAction {
public ActionForward performAction(ActionMapping mapping, ActionForm form,
UserBean user, HttpServletRequest request, HttpServletResponse response)
throws Exception {
// Get the user ID and selected oid
String user ID = user.getUserName();
ContactForm contactForm = (ContactForm) form;
String oid = contactForm.getSelectedContact();
// Delete the contact
ContactHelper contactHelper = ContactHelper.getInstance();
contactHelper.deleteContact(user ID, oid);
// Render the main edit page
return (mapping.findForward("mainedit"));
}
} |
The abstract action class extends org.apache.struts.action.Action and implements the execute method. This implementation ensures that we
are logged in, then invokes the called Action classes and the performAction
method, passing the same parameters that were passed to the execute method,
plus the user bean that was retrieved during login validation. See Listing
2. (When the application is moved to a portal environment, the login action
will be removed, and the code will be changed to retrieve the user object
from the portletRequest object. At that point, any application code that
accesses the user object can be changed to use the WPS user object API.)
Listing 2. PostLoginAbstractAction Class
class PostLoginAbstractAction extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
// Check for existing session with user key. Go to login if
// not found.
UserBean user = null;
HttpSession session = request.getSession(false);
if (session != null)
user = (UserBean) session.getAttribute("user");
if (user == null)
return (mapping.findForward("login"));
// Invoke the action class performAction method
return performAction(mapping, form, user, request, response);
}
public abstract ActionForward performAction(ActionMapping mapping, ActionForm form,
UserBean user, HttpServletRequest request, HttpServletResponse response)
throws Exception;
} |
The remaining Action class implementations are very similar. As shown in the DeleteContactAction class (Listing 1), some of our action classes will use the services of our persistence classes. Again, the persistence function is provided as part of the download implementation.
The ContactListBroker interface (Listing 3) defines the function that our application needs for Contact list processing. (To see how specific brokers implement this function, view the code in the available in Downloads.)
Listing 3. ContactListBroker Interface
{
/**
* Get the Contact information given a contact number
* @param owner java.lang.String
* @param contactOID java.lang.String
* @return ContactForm
*/
public ContactForm getContact(String owner, String contactOID) throws AIMException;
/**
* Create a Contact
* @param ContactForm
*/
public void saveContact(ContactForm contactForm) throws AIMException;
/**
* Delete a Contact
* @param owner java.lang.String
* @param contactOID java.lang.String
*/
public void deleteContact(String owner, String oid) throws AIMException;
/**
* Get the contact list.
* @param owner java.lang.String
* @return List of ContactForm objects
*/
public List getContactList(String owner) throws AIMException;
} |
There are three beans defined for this application:
- UserBean: represents a logged in user. For our application, this bean only maintains the user ID and password.
- ValidateUserBean: responsible for authentication processing for the user ID and password provided for login. Here, this implementation is trivial, since any user ID and password will be accepted (with the single exception of the password "invalid", provided for test purposes).
- The final bean is a helper class that provides common function to add a contact and to delete a contact. (Not shown in this article, but available in the download.)
There are two form bean implementations for this application, as defined
in the struts-config.xml file, that extend org.apache.struts.validator.ValidatorForm and contain properties (with accessors) applicable to both:
- ContactForm: properties will typically map to the columns in the contacts table. See Setting up the database for the sample table definition.
- LoginForm: properties are, minimally, user ID and password.
Application resource properties
The message properties file, named application.properties and located in
a resources directory, is defined in the struts-config.xml file. This file
contains two properties needed for the application, loginForm.username
and loginForm.password, as well as standard properties for error messages.
The final components of our application are the JSP files. Based on our
transition diagram, and as defined in the struts-config.xml file, we need to implement five JSPs, each needed for rendering:
- Add Contact page
- Modify Contact page
- Main view page
- Main Edit page
- Detail view page
- Login page.
The index.jsp file will also need to be updated to provide the login screen for the
application. This JSP will use the Login Action and the LoginForm bean.
After successfully logging in, the user will be at the Main view page (Figure
3), showing the list of contacts specific to that user.
Figure 3. Main List View

Listing 4 shows key portions of some JSP files: the JSP for rendering the Main view, followed by an example of the rendered content for this JSP. A session-scoped bean that contains the list of contacts is required for this JSP. Several standard Struts tag libraries are defined for use within the JSP, and the rewrite tag from the struts-html tag library is used to link to the appropriate cascading style sheets. The form tag from the same library is also used to define an action associated with this view. In this case, when the user selects a contact in the rendered list, we want processing to continue with the detailview action passing the selected contact as a parameter with the request form data. A second form is also added to this view giving the user the option to go to the Main Edit view.
Listing 4. Main View JSP
<jsp:useBean id="contactList" class="java.util.List" scope="request" />
<%@ taglib uri="/WEB-INF/tld/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/tld/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/tld/struts-logic.tld" prefix="logic" %>
<LINK href="<html:rewrite page='/theme/Styles.css'/>" rel="stylesheet" type="text/css">
<LINK href="<html:rewrite page='/theme/Master.css'/>" rel="stylesheet" type="text/css">
...
<table border="0" width="95%" align="center" cellspacing="0" cellpadding="0" >
...
<tbody>
<html:form method="post" action="/detailview">
<input type="hidden" name="selectedContact">
<logic:iterate name="contactList" id="aContact">
<tr>
<td valign="top" align="left">
<a href="javascript:document.contactForm.selectedContact.value=
'<bean:write name="aContact" property="oid"/>';document.contactForm.submit()">
<bean:write name="aContact" property="firstName"/>
<bean:write name="aContact" property="lastName"/>
</a>
</td>
<td class="wpsPortletSmText" valign="top" align="left">
<bean:write name="aContact" property="company"/>
</td>
<td class="wpsPortletSmText" valign="top" align="left">
<bean:write name="aContact" property="email"/>
</td>
<td class="wpsPortletSmText" valign="top" align="left">
<bean:write name="aContact" property="mobilePhone"/>
</td>
</tr>
</logic:iterate>
</html:form>
<tr>
<td colspan="4">
<html:link forward="mainedit">
<html:img page="/images/task_edit.gif" title="Edit" border="0"/>Edit</html:link>
</td>
</tr>
</tbody>
</table>
... |
Our application will be created with two options for data persistence:
one will be a database broker implementation, the other will be an in-memory
implementation. For testing purposes, it may be desirable to just work
with the in-memory version, since it eliminates the need for the following
database setup steps; however, the data will be lost each time the application
server is restarted. The broker type is selected through a configuration
parameter set in the Web deployment descriptor file, web.xml.
- To persist the application data to a database, a database table will need to be created 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 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) ) |
- Add an entry into the contacts list table to make it easier to test the
portlet as you develop it. Execute the following SQL command to create
a new entry. If you are going to log in to the portal using a user ID other
than the test ID
wpsadmin, be sure to replace wpsadmin with this user ID in the SQL statement.
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') |
- Create a datasource in WebSphere Application Server for the database where
you created the Contacts table. In the application server admin console
(Figure 4), select Resources => JDBC Providers, then expand the appropriate JDBC Driver. Right-click on Data Sources, and then select New.
- Enter the datasource name for the JNDI name value.
- Enter the database name, user, and password values.
- Test the connection.
Figure 4. Create a datasource in WebSphere Application Server

- If you intend to use WebSphere Studio Application Developer to test and
debug this Struts application, create a datasource in Application Developer
for this same database. From the Application Developer Server perspective
(Figure 5), edit the WebSphere Administrative Domain (
server-cfg.xmlfile), select the datasources tab on the edit pane, select the appropriate JDBC driver, and add the datasource, providing the appropriate attribute values.
Figure 5. Create a datasource in WebSphere Studio Application Developer

The following tables list the Java classes implemented for our example application. Of course, other implementations may vary, but this defines the classes for the completed implementation available in the download, and a brief description of each class.
| Package | Class | Description |
| com.ibm.aim.beans | ContactHelper | Helper class to invoke persistence management functions for contacts |
| UserBean | Represents the logged in user | |
| ValidateUserBean | Validate the user id and password. | |
| com.ibm.aim.controller | AddContactAction | Handles requests to add a contact |
| DeleteContactAction | Handles requests to delete a contact | |
| DetailViewAction | Handles requests to show the detail contact view | |
| ModifyAction | Handles requests to modify a contact | |
| ModifyContactAction | Handles requests to save the edited contact changes | |
| MainEditAction | Handles requests to show the main edit view | |
| MainViewAction | Handles requests to display the main contact list view | |
| PostLoginAbstractAction | Abstract class for post login actions | |
| LoginAction | Handles login request | |
| com.ibm.aim.view | ContactForm | Struts form object that represents a Contact |
| LoginForm | Struts form object that represents a login request | |
| com.ibm.aim.persistence | BrokerFactory | Creates a concrete Broker instance |
| ContactListBroker | Persistence class interface | |
| DbAbstractBroker | Abstract class for common function for a database broker | |
| DbBrokerImpl | A database broker implementation extends DbAbstractBroker and implements ContactListBroker | |
| DbBrokerParms | Defines the configuration parameters needed for access the database | |
| DbBrokerParmsImpl | Implementation of the configuration parameters needed for access the database | |
| MemoryBrokerImpl | Memory broker implementation implements ContactListBroker | |
| com.ibm.aim.utilities | AIMException | Base exception class |
| AIMLoginException | Exception for Login failure | |
| AIMWrapperException | An AIMException that wraps another (`underlying') exception | |
| RetryException | Exception for database failure indicating a needed retry |
Complete implementation and test
You should now be able to complete the implementation and test the results. (You can also get the completed Struts application from the available download.) To test the implementation in Application Developer, simply select the index.jsp file in the navigator pane, right-click and select Run on Server.
This will create a server instance and start index.jsp in a web browser in Application Developer. If you are using this method
to create a new server instance and are using the Database Persistence
Manager, you will need to complete the steps to define a datasource in
Application Developer (Figure 5) before this application will run successfully.
Be sure to complete and test the example application successfully before moving on to the next section, which describes the steps to run the Struts application as a portlet.
WebSphere Portal Struts Portlet Framework
As mentioned earlier, the Struts Portlet Framework is provided with WebSphere Portal Version 4.2 as a component in the base product. The framework supports the deployment of Struts WAR files as portlets in WebSphere Portal 4.2, and also provides support for portal modes and device support within Struts-based applications.
In WebSphere Portal 4.2 the supported Struts framework is Struts 1.1 Beta
2. In a standard installation, you will find the Struts framework in the
<wp_root>\dev\struts\ directory.
Refer to the WebSphere Portal 4.2 InfoCenter for more information and restrictions on the use of the Struts Portlet Framework. From the InfoCenter main page, select Developing portlets => Struts Portlet Framework.
Modifications needed to run in WebSphere Portal
To run our contacts list Struts application in the portal environment, no changes are required to our JavaTM code. All action, form bean, application bean, persistence, and utility classes also remain unchanged. However, changes are needed in some of our application configuration files. These changes are discussed next.
Using Struts modules to support Portal modes
Support for dividing an application into modules was built into the framework
by Struts 1.1. When using modules, an application's configuration information
and directory structure are separated, so instead of a single struts-config.xml
file for the entire application, we now have multiple control files with
separate directory structures, identified by module-relative portions of
the application URI.
The Struts Portlet Framework takes advantage of this module support to
provide both mode and device (markup) differentiation of the Struts configuration.
There are two paths that can be specified in the web.xml for supporting
modes and device types differentiation:
- The first is the search path, used to determine which module (and which
struts-config.xmlfile) to use. It also determines the base directory for locating JSPs. The following init param setting will set the search path to consider markup name and mode when searching for modules:<init-param> <param-name>SubApplicationSearchPath</param-name> <param-value>markupName, mode</param-value> </init-param>
- The second path is the include path, used to include common JSP files in the search. The search will progress from the most qualified to the least qualified, based on the path settings. The search completes when the first file match is found:
<init-param> <param-name>IncludesSearchPath</param-name> <param-value>locale</param-value> </init-param>
Both the SubApplicationSearchPath and IncludesSearchPath are Struts Portlet Framework-defined initialization parameters.
Web Deployment Descriptor: web.xml
In order to run our Struts application as a portlet, we need to change
the existing web.xml file:
- Add the two init parameters specified above (SubApplicationSearchPath and IncludesSearchPath) to this deployment descriptor.
- Change the
<servlet-class>back tocom.ibm.wps.portlets.struts.WpsStrutsPortlet. - Change the servlet mapping. We need to specify a servlet URL mapping for
the portlet, so change the servlet mapping to use the following path prefix
servlet mapping:
<servlet-mapping id="Unique_ServletMapping"> <servlet-name>Struts</servlet-name> <url-pattern>/Struts/*</url-pattern> </servlet-mapping>
- Of course, a normal Struts application requires a servlet mapping to associate
paths to actions. The Struts Portlet Framework handles this using a pseudo
servlet mapping that needs to be specified as an init parameter:
<init-param> <param-name>struts-servlet-mapping</param-name> <param-value>*.do</param-value> </init-param>
- Add the init parameters below. We will create three modules for our application: view, edit and help. These modules are associated with device type html, and are configured as init parameters
/config/<device>/<mode>, so we have defined:- config/html/view
- config/html/edit
- config/html/help.
The param value associated with each of these module definitions indicates where to start to search for the Struts configuration file.
Listing 5. web.xml file changes
<init-param>
<param-name>config/html/view</param-name>
<param-value>/WEB-INF/html/view/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>config/html/edit</param-name>
<param-value>/WEB-INF/html/edit/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>config/html/help</param-name>
<param-value>/WEB-INF/html/help/struts-config.xml</param-value>
</init-param> |
- Finally, change the welcome file list. The Struts Portlet Framework allows for the specification of the initial view for each device/mode. This is specified through the welcome file list by providing a file (typically HTML or JSP) for each module by indicating the application prefix:
<welcome-file-list>
<welcome-file>html/view/index.jsp</welcome-file>
<welcome-file>html/help/index.jsp</welcome-file>
<welcome-file>html/edit/index.jsp</welcome-file>
</welcome-file-list> |
Portlet Deployment Descriptor: portlet.xml
In order to run our Struts application as a portlet, we also need to create
a portlet.xml file. Keep in mind that the portlet href attribute defined
in portlet.xml must map to the servlet id in the web.xml file.
Listing 6. portlet.xml
Listing 6. portlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE portlet-app-def PUBLIC "-//IBM//DTD Portlet Application 1.1//EN" "portlet_1.1.dtd">
<portlet-app-def>
<portlet-app uid="DCE:b067c2c0-1eba-1211-0000-02884891a5dc:1"
major-version="1" minor-version="0">
<portlet-app-name>StrutsContactsWeb application</portlet-app-name>
<portlet id="Portlet_1" href="WEB-INF/web.xml#Servlet_1" major-version="1"
minor-version="0">
<portlet-name>StrutsContactsWeb portlet</portlet-name>
<cache>
<expires>0</expires>
<shared>NO</shared>
</cache>
<allows>
<maximized/>
<minimized/>
</allows>
<supports>
<markup name="html">
<view/>
<edit/>
<help/>
</markup>
</supports>
</portlet>
</portlet-app>
<concrete-portlet-app uid="DCE:b067c2c0-1eba-1211-0000-02884891a5dc:1.1">
<portlet-app-name>StrutsContactsWeb application</portlet-app-name>
<concrete-portlet href="#Portlet_1">
<portlet-name>StrutsContactsWeb portlet</portlet-name>
<default-locale>en</default-locale>
<language locale="en">
<title>StrutsContactsWeb portlet</title>
<title-short></title-short>
<description></description>
<keywords>WPS, Struts</keywords>
</language>
<config-param>
<param-name>FilterChain</param-name>
<param-value>StrutsTranscoding</param-value>
</config-param>
</concrete-portlet>
</concrete-portlet-app>
</portlet-app-def> |
Struts Configuration: struts-config.xml
Since the param value associated with each of our three application module
definitions indicates where the search starts for the module-specific Struts
configuration files, we now need to create new struts-config.xml files.
These will reside in
/WEB-INF/html/view//WEB-INF/html/edit//WEB-INF/html/help/.
These files will contain the same Struts configuration information that we had in our original servlet-based application.
- Change the
struts-config.xmlfile in the/WEB-INFdirectory to a minimal configuration definition. We will move the configuration information that was in this file to the three new module specificstruts-config.xmlfiles.
Listing 7. struts-config.xml
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
<action-mappings></action-mappings>
<controller processorClass="com.ibm.wps.portlets.struts.WpsRequestProcessor">
</controller>
<message-resources parameter="resources.application"/>
</struts-config> |
- Next, create the new
struts-config.xmlfiles, as shown in Listings 8, 9 and 10.
Listing 8. VIEW - /WEB-INF/html/view/struts-config.xml
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
<!-- ========== Form Bean Definitions =================================== -->
<form-beans>
<form-bean name="logonForm" type="com.ibm.aim.view.LoginForm"/>
<form-bean name="contactForm" type="com.ibm.aim.view.ContactForm"/>
</form-beans>
<!-- ========== Global Forward Definitions ============================== -->
<global-forwards>
<forward name="login" path="/index.jsp"/>
<forward name="mainview" path="/mainview.do" />
</global-forwards>
<!-- ========== Action Mapping Definitions ============================== -->
<action-mappings>
<action
path="/validatelogin"
type="com.ibm.aim.controller.LoginAction"
name="logonForm"
scope="request"
input="/index.jsp">
</action>
<action
path="/mainview"
type="com.ibm.aim.controller.MainViewAction"
name="contactForm"
scope="request"
validate="false" >
<forward
name="success"
path="/main_view.jsp"/>
</action>
<action
path="/detailview"
type="com.ibm.aim.controller.DetailViewAction"
name="contactForm"
scope="request"
validate="false" >
<forward
name="detailviewpage"
path="/detail_view.jsp"/>
</action>
</action-mappings>
<!-- ================================ Controller Configuration -->
<controller processorClass="com.ibm.wps.portlets.struts.WpsRequestProcessor">
</controller>
<!-- ========== Message Resources Definitions =========================== -->
<message-resources parameter="resources.application"/>
<!-- ========== Plug Ins Configuration ================================== -->
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property property="pathnames"
value="/WEB-INF/validator-rules.xml, /WEB-INF/validation.xml"/>
</plug-in>
</struts-config> |
Listing 9. EDIT - /WEB-INF/html/edit/struts-config.xml
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
<form-beans>
<form-bean name="contactForm" type="com.ibm.aim.view.ContactForm"/>
</form-beans>
<global-forwards>
<forward name="addcontactpage" path="/add_contact.jsp"/>
<forward name="mainedit" path="/mainedit.do" />
</global-forwards>
<action-mappings>
<action
path="/mainedit"
type="com.ibm.aim.controller.MainEditAction" >
<forward
name="maineditpage"
path="/main_edit.jsp"/>
</action>
<action
path="/modify"
type="com.ibm.aim.controller.ModifyAction"
name="contactForm"
scope="request"
validate="false" >
<forward
name="editcontactpage"
path="/WEB-INF/pages/edit_contact.jsp"/>
<forward
name="deletecontact"
path="/deletecontact.do"/>
</action>
<action
path="/modifycontact"
type="com.ibm.aim.controller.ModifyContactAction"
name="contactForm"
scope="request"
validate="false" >
<forward
name="mainedit"
path="/mainedit.do"/>
</action>
<action
path="/deletecontact"
type="com.ibm.aim.controller.DeleteContactAction"
name="contactForm"
scope="request"
validate="false" >
<forward
name="maineditpage"
path="/main_edit.jsp"/>
</action>
<action
path="/addcontact"
type="com.ibm.aim.controller.AddContactAction"
name="contactForm"
scope="request"
validate="false" >
<forward
name="addcontactpage"
path="/add_contact.jsp"/>
</action>
</action-mappings>
<controller processorClass="com.ibm.wps.portlets.struts.WpsRequestProcessor">
</controller>
<message-resources parameter="resources.application"/>
</struts-config> |
Listing 10. HELP - /WEB-INF/html/help/struts-config.xml
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
<action-mappings>
</action-mappings>
<controller processorClass="com.ibm.wps.portlets.struts.WpsRequestProcessor">
</controller>
<message-resources parameter="resources.application"/>
</struts-config> |
Note that we have changed the Welcome File List in the web.xml file, which is used to define the starting point for each of our defined modules. Beginning from the root context of the Web application, we need to create directories for edit, view, and help, and include in those directories the index.jsp for each of those modes.
Notice also that our struts-config.xml files for each module (representing
a portal mode) show no additional path information for our JSP files, so
in each of those same directories, we need to add the appropriate JSPs
for that module. Therefore, we have:
- In the
\html\editdirectory:add_contact.jspedit_contact.jspmain_edit.jspindex.jsp file.
- In the
\html\viewdirectory:detail_view.jspmain_view.jsp.
- In the
\html\helpdirectoryindex.jspfile.
Also, each relative reference in each module JSP that points to resources (such as images and cascading style sheet files) would be relative to that same directory. For our application, then, we have an image and theme directory for each module with the appropriate files defined.
The version of the Struts Portlet Framework code that is shipped with WebSphere
Portal 4.2 may require a fix to the struts-html.tld file to prevent links
for style sheets in our JSPs to fail. This fix can be made simply by editing
the struts-html.tld and changing the rewrite tag to use the org.apache.struts.taglib.html.RewriteTag
class. The resulting code should look like the following:
<tag>
<name>rewrite</name>
<tagclass>org.apache.struts.taglib.html.RewriteTag</tagclass> |
As part of minor cleanup, remove the Edit button from the main view JSP, since we will go to Edit mode using the portal framework instead. Similarly, remove the Return button from the Main Edit JSP, since we will return back to the previous mode using the portal framework.
After making these configuration and structural changes to the Struts application, rebuild the WAR file and deploy the application to WebSphere Portal. There is no Portal setup work needed to make this application work in this environment.
Exercise: Remove the login and use the Portal user
When we invoke the contact list portlet, we don't want an additional login step since we are already logged into WebSphere Portal. Therefore, we want to change the portlet to:
- remove the Login page
- recognize the portal user object.
To remove the Login page, we could change the struts configuration for
the view mode initial page (that was in the welcome list in web.xml), changing it from the login JSP. But since we want to start from an action
(the Main view action), instead of a JSP, we can still start with index.jsp, but change it to immediately forward to the Main view action. We already
do something similar to this in edit mode, so look there for a reference
and make the appropriate change in index.jsp in for the view mode.
Our abstract action class will need to change to get the User object that WebSphere Portal maintains. The User object is available from the Request object when running in the portal environment, meaning that you can get the User object from PortletRequest. However, our base application, not written as a portlet application, gets passed an HttpServletRequest in its execute methods. We will need to cast it from an HttpServletRequest object to PortletRequest. That way, we can get the User object from a request with request.getUser() method, which will give us back an instance of org.apache.jetspeed.portlet.User. There is really no need to put this object on a session anymore, since Portal will maintain it for us and make it available from the request. For this to compile successfully, you will need to add the portlet-api.jar file to the project build path.
This suggests that there must be a logged-in user. Check to see that the user has been authenticated and that a User object is available. Otherwise, throw an error indicating that the user is not authenticated. The performAction signature on our Action classes will also need to be changed to remove UserBean from the parameter list, since we will get the portal User object from the request as it is needed. Finally, we need to change the methods that access userName from the UserBean instance to get the user ID from the User object, using the getuser ID method.
Save, recreate the portlet WAR file, and retest.
This article discussed the implementation of a sample application using the Struts framework, then discussed the modifications necessary to deploy that same application in the WebSphere Portal environment as a portlet. We showed the use of Struts modules and how they could be used in conjunction with the WebSphere Struts Portlet Framework to support multiple portal modes.
The complete implementation for this sample is provided in the Downloads section below, in which two files are available:
ContactStrutsServlet.zip: contains the servlet-based struts implementation including the Java source. This file can be imported into WebSphere Studio Application Developer.ContactStrutsPortlet.war: contains the portlet version of the struts application. This file can be installed in WebSphere Portal Version 4.2. The changes needed to run in the portal environment are included in this file. Since there are no source code changes needed, source is not included in this file. However, you can use the source files fromContactStrutsServlet.zipfor reference.
| Name | Size | Download method |
|---|---|---|
| ContactStrutsPortlet.war | 1.3 MB | FTP |
| ContactStrutsServlet.zip | 1.3 MB | FTP |
Information about download methods
- WebSphere Portal 4.2 Info Center
- The Apache Struts Web Application Framework
- Struts in WebSphere Portal 4.1
Tim Hanis is the lead architect in WebSphere Premises Server development at IBM in Research Triangle Park, NC. He has led a number of development projects within IBM and has extensive experience helping customers solve business problems with WebSphere middleware products.

Jim Bonanno is a senior software engineer for IBM WebSphere Portal at the IBM Research Triangle Park Lab in Raleigh, North Carolina. You can reach Jim at bonanno@us.ibm.com.
Lisa Tomita is a senior consultant for IBM Software Services for WebSphere at the IBM Research Triangle Park Lab in Raleigh, North Carolina. You can reach Lisa at tomato@us.ibm.com.




