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 developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

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]

IBM WebSphere Developer Technical Journal : Developing a Struts Application for WebSphere Portal 4.2

Tim Hanis (hanistt@us.ibm.com), WebSphere Premises Server Chief Architect, IBM Raleigh Lab
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 (bonanno@us.ibm.com), Senior Software Engineer, IBM Raleigh Lab
Jim Bonanno
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 (tomato@us.ibm.com), Senior Consultant, IBM Raleigh Lab
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.

Summary:  This article will discuss the implementation of a Web application, written first as a servlet-based Struts application, and then apply modifications so that the Struts application can then be deployed as a portlet in IBM WebSphere Portal.

Date:  26 Mar 2003
Level:  Intermediate

Activity:  6030 views
Comments:  

Introduction

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)

Example Application

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:

ViewDisplays
Loginuser login view
Mainlist of contacts with an option to select one contact for more information
Detailselected contact's detail information
Main Editlist of contacts with options to add, delete, or modify
Add Contacta form view to enter information for a new contact
Modify Contacta 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.


Application Design

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
Transition diagram

Application Implementation

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:

  1. 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 for cm.jar from the project properties dialog.
    Figure 2. Create a Web project
    Create a Web project
  2. Next, import the PortalStrutsBlank.war file (see Downloads) into the project (overwriting the web.xml file that was added when the project was initially created). In a standard WebSphere Portal 4.2 installation, you will find the PortalStrutsBlank.war file in the <wp_root>\dev\struts\StrutsPortlet directory. 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).
  3. A default application.properties file 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 the source/resources directory to the WEB-INF/classes/resources directory.
  4. 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.
  5. The import will also bring in a portlet.xml file 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.
  6. Remove the jdbc2_0-stdext.jar file from the lib directory. Otherwise, a ClassCastException may occur on com.ibm.ejs.cm.JDBC1PhaseRF because of conflicts in this JAR and the j2ee.jar.
  7. 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.jar
  • WAS_PLUGINDIR/lib/webcontainer.jar
  • WAS_PLUGINDIR/lib/ivjejb35.jar
  • WAS_PLUGINDIR/lib/websphere.jar
  • WAS_PLUGINDIR/lib/cm.jar
  • SERVERJDK_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> to org.apache.struts.action.ActionServlet. This will be changed back to com.ibm.wps.portlets.struts.WpsStrutsPortlet when 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.xml file to support the persistence classes that are part of the download: persist_to_db and datasource. Setting persist_to_db to "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.xml file:
            
<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.xml file 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: userName and password, both required. There will also be a resources properties file that defines the label names loginForm.username and loginForm.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>

Action class implementations

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;

}

Application beans

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

Struts form beans

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.

Java Server Page (JSP) Files

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:

  1. Add Contact page
  2. Modify Contact page
  3. Main view page
  4. Main Edit page
  5. Detail view page
  6. 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
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>
...

Setting up the database

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
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.xml file), 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
Create a datasource in WebSphere Studio Application Developer

Implementation class summary

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 ContactHelperHelper class to invoke persistence management functions for contacts
UserBeanRepresents the logged in user
ValidateUserBeanValidate the user id and password.
com.ibm.aim.controller AddContactActionHandles requests to add a contact
DeleteContactActionHandles requests to delete a contact
DetailViewActionHandles requests to show the detail contact view
ModifyActionHandles requests to modify a contact
ModifyContactActionHandles requests to save the edited contact changes
MainEditActionHandles requests to show the main edit view
MainViewActionHandles requests to display the main contact list view
PostLoginAbstractActionAbstract class for post login actions
LoginActionHandles login request
com.ibm.aim.view ContactFormStruts form object that represents a Contact
LoginFormStruts form object that represents a login request
com.ibm.aim.persistence BrokerFactoryCreates a concrete Broker instance
ContactListBrokerPersistence class interface
DbAbstractBrokerAbstract class for common function for a database broker
DbBrokerImplA database broker implementation extends DbAbstractBroker and implements ContactListBroker
DbBrokerParmsDefines the configuration parameters needed for access the database
DbBrokerParmsImplImplementation of the configuration parameters needed for access the database
MemoryBrokerImplMemory broker implementation implements ContactListBroker
com.ibm.aim.utilities AIMExceptionBase exception class
AIMLoginExceptionException for Login failure
AIMWrapperExceptionAn AIMException that wraps another (`underlying') exception
RetryExceptionException 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.xml file) 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 to com.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.xml file in the /WEB-INF directory to a minimal configuration definition. We will move the configuration information that was in this file to the three new module specific struts-config.xml files.

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.xml files, 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>

Directory structure changes

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\edit directory:
    • add_contact.jsp
    • edit_contact.jsp
    • main_edit.jsp
    • index.jsp file.
  • In the \html\view directory:
    • detail_view.jsp
    • main_view.jsp.
  • In the \html\help directory
    • index.jsp file.

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.

Final Cleanup

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.

Deploy and test the portlet

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:

  1. remove the Login page
  2. 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.


Conclusion

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 from ContactStrutsServlet.zip for reference.


Downloads

NameSizeDownload method
ContactStrutsPortlet.war 1.3 MBFTP|HTTP
ContactStrutsServlet.zip 1.3 MBFTP|HTTP

Information about download methods


Resources

About the authors

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

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.

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 developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

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

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere
ArticleID=14574
ArticleTitle=IBM WebSphere Developer Technical Journal : Developing a Struts Application for WebSphere Portal 4.2
publish-date=03262003
author1-email=hanistt@us.ibm.com
author1-email-cc=
author2-email=bonanno@us.ibm.com
author2-email-cc=
author3-email=tomato@us.ibm.com
author3-email-cc=

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

For articles in technology zones (such as Java technology, Linux, Open source, XML), Popular tags shows the top tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), Popular tags shows the top tags for just that product zone.

For articles in technology zones (such as Java technology, Linux, Open source, XML), My tags shows your tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), My tags shows your tags for just that product zone.

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Try IBM PureSystems. No charge.

Special offers