Write portlets

 


Contents

  1. Overview
  2. Create a simple portlet instance
  3. Sample portlets
  4. Set up a portlet development environment
  5. Hello World portlet
  6. Compile Java source
  7. Package and deploying portlets
  8. Generating markup
  9. Using persistence
  10. Portlet messaging
  11. Message and trace logging
  12. Refresh the portlet cache
  13. Parallel portlet rendering
  14. Personalize the portlet

 


Overview

If you are familiar with developing portlets for version 2.1 of the WebSphere Portal Family of products, there have been significant improvements to the Portlet API.

 

Create a simple portlet instance

WebSphere Portal includes the following set of predefined portlets are available in the wp_root/install directory.

Portlet Filename
File server portlet, serves up static HTML files fileserverportlet.war
Servlet invoker portlet, invokes a servlet as a portlet ServletInvoker.war
JSP portlet, serves JSPs jspserverportlet.war
CSV Viewer, displays a file with data arranged in comma-separated values format csv.war
RSS portlet, displays remote URLs that provide data using rich site summary format. xslt.war
OCS viewer, displays Open Content Syndication channel feeds. ocsviewer.war

These portlets provide basic functions, such as serving static HTML or dynamic JSP files, so you do not have to write the code to integrate these functions into your portal. You can create and deploy any number of instances of these portlets using Portal Administration.

 

Use the file server portlet to serve static content

The file server portlet can display any HTML file in its path to the portlet window. By default, it displays...

$WAS_ROOT/installedApps/portlet_id.ear/FileServer.war/FileServerPortlet/html/test.html

...directory of the WAR file. To serve other content, add the HTML file to the path of the file server portlet and modify its url parameter to the new file using Manage Portlets in Portal Administration. The root of the url parameter points to...

...where portlet_id is the unique identifier created during deployment. You can create multiple instances of this portlet, each serving different content from its path.

 

Sample portlets

A set of sample portlets are provided that demonstrate the features of the Portlet API. These portlets are provided in the file bookmark_samplets.zip in the wp_root/dev directory. These sample portlets include the following:

bookmark0.war Shows how to get, set, and remove variables for a concrete portlet. The portlet, however, does not produce any output.
bookmark1.war Uses the PortletResponse to write output to the portal.
bookmark2.war Implements listeners that, for example, insert HTML output in the page prior to portlet markup.
bookmark3.war Retrieves parameters from PortletConfig and attributes from PortletData and PortletSettings.
bookmark4.war Gets localized text from a resource bundle.
bookmark5.war Includes a JSP for the portlet's view mode.
bookmark6.war Includes a JSP for edit mode and implements an ActionListener so the user can add bookmarks.

The next few sections describe how to create a simple Hello World portlet and compile, package, and deploy it. No file sample is provided for Hello World; you have to create it using the examples provided in the text. However, a HelloWorld2.war is provided in the wp_root/dev directory that demonstrates how to provide portlet markup using a JSP.

 

Set up a portlet development environment

Before trying any of the classes and samples discussed in this section, setup an environment that makes the tasks of writing, compiling, and testing portlets easier. The WebSphere Portal product package includes the following development tools:

In addition to a development workstation, setup a portal server to which you can publish and test your portlet application projects by installing the following components:

 

Hello World portlet

The Hello World portlet provides an introduction to writing your first portlet. Hello World extends the AbstractPortlet helper class and provides the fewest methods required for a portlet. It uses the PortletResponse to provide simple output to the portal page.

Example: Hello World portlet



package com.ibm.wps.samples.HelloWorld;

import org.apache.jetspeed.portlet.*;
import org.apache.jetspeed.portlets.*;
import java.io.*;

public class HelloWorld extends AbstractPortlet
{
  public void init(PortletConfig portletConfig) throws UnavailableException
  {
    super.init( portletConfig );
  }
  public void service( PortletRequest portletRequest, 
                       PortletResponse portletResponse )
     throws PortletException, IOException
  {
    PrintWriter writer = portletResponse.getWriter();

    writer.println("<p>Hello Portal World!</p>");
  }
}



 

Compile Java source

Use the JDK provided by WebSphere Application Server to compile your Java source files. Before you compile your Java source, set the CLASSPATH for the compiler to find the JAR files for any portlet packages that your portlet uses. The following JAR files should always be set in the CLASSPATH to compile:


   was_root/lib/app/portlet-api.jar;
   was_root/lib/app/wpsportlets.jar;
   was_root/lib/app/wps.jar;

where was_root is the directory where WebSphere Application Server is installed. Additionally, if you require any classes for servlet functionality, add the following:


   was_root/lib/j2ee.jar;
   was_root/lib/websphere.jar;

Then, compile the portlet using the fully-qualified path to the Java portlet source.

  javac -classpath %CLASSPATH% com.ibm.wps.samples.HelloWorld.java

To test Hello World after it is compiled, first package it as a WAR file and install it to the portal server. First, however, the portlet must be packaged in the JAR file format. To create a JAR file with the name HelloWorld.jar, enter the following command:

   jar -cf HelloWorld.jar HelloWorld.class

Be aware that portal server class loading follows the WebSphere Application Server hierarchy for classpaths and search orders. Classes that are needed by the portlet must be loaded by either the portlet's classloader or one of its parent classloaders.

 

Package and deploying portlets

After you have finished developing and testing your portlet, the portlet is ready to be deployed to portal servers in the form of a Web application ARchive or WAR file. You also need to package your portlet as a WAR file to test it on the portal server.

The WAR file format contains the Java classes and resources that make up a single portlet. The resources can be images, JSP files, or property files containing translated message text. In addition to the portlet code and resources, the WAR file contains a Web application deployment descriptor (web.xml) and portlet deployment descriptor, portlet.xml, that contains the information necessary for the portal server to install and configure the portlet. Package portlet classes, resources, and descriptive information in a single file makes distribution and deployment of portlets easier.

WebSphere Portal includes an administrative portlet for installing, uninstalling, and updating portlets. Portlets contained in WAR files have the advantage of being dynamically downloaded and installed. The portal administrator can download a WAR file from the Internet and then use the portal administration interface to install the portlet to WebSphere Portal. The portlet is ready for use and does not require the server to be restarted.

To package your portlet in a WAR file, follow these steps:

  1. Create the deployment descriptors .
  2. Arrange portlet files in the WAR file directory structure .
  3. Use the JAR utility to package the files .

 

Deployment descriptors for Hello World

The following samples can be packaged with the Hello World portlet.

Web application deployment descriptor:



<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
                         "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
<web-app id="WebApp_504848313">
  <display-name>Hello World Portlet Application - Portlet Sample #1</display-name>
    <servlet id="Servlet_439329280">
        <servlet-name>HelloWorld</servlet-name>
        <servlet-class>com.ibm.wps.samples.HelloWorld</servlet-class>
    </servlet>
    <servlet-mapping id="ServletMapping_439329280">
        <servlet-name>HelloWorld</servlet-name>
        <url-pattern>/HelloWorld/*</url-pattern>
    </servlet-mapping>
</web-app>


Portlet deployment descriptor:



<?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="504848313">
    <portlet-app-name>Hello World Portlet Application - Portlet Sample #1</portlet-app-name>
    <portlet href="WEB-INF/web.xml#Servlet_439329280" id="Portlet_439329280">
      <portlet-name>HelloWorld</portlet-name>
      <cache>
        <expires>0</expires>
        <shared>no</shared>
      </cache>
      <allows> <minimized/> </allows>
      <supports>
        <markup name="html">
          <view/>
        </markup>
      </supports>
    </portlet>
  </portlet-app>
  <concrete-portlet-app uid="640682430">
    <portlet-app-name>Concrete Hello World Portlet Application - Portlet Sample #1</portlet-app-name>
      <context-param>
        <param-name>Portlet Master</param-name>
        <param-value>yourid@yourdomnain.com</param-value>
      </context-param>
      <concrete-portlet href="Portlet_439329280">
        <portlet-name>HelloWorld</portlet-name>
        <default-locale>en</default-locale>
         <language locale="en_US">
           <title>Hello World - Sample Portlet #1</title>
           <title-short>Hello-World</title-short>
           <description>Hello World - Sample Portlet #1</description>
           <keywords>portlet hello world</keywords>
         </language>
       </concrete-portlet>
    </concrete-portlet-app>
</portlet-app-def>


 

WAR file directory structure

Before you package your portlet, the class files and resources must be arranged in the directory structure described here. A portlet application exists as a structured hierarchy of directories.

/

The root directory of the portlet file structure.

/images

Location for any images the required by the portlet.

/WEB-INF

Location for all protected resources. The /WEB-INF directory stores the portlet descriptor document and all of the runtime executable JAR files and classes that the packaged portlet requires.

The portlet information directory is not part of the public document tree of the application. Files that reside in /WEB-INF are not served directly to a client.

/WEB-INF/lib

Location for storing portlet JAR files.

/jsp

Location for JSP files. This is a suggested path name. Your JSPs can be packaged in any location outside of the /WEB-INF directory.

/WEB-INF/classes

Location for portlet class files. Individual class files should be stored in a directory structure within /WEB-INF/classes that reflects the class package. For example, the class HelloWorld.class, in package com.ibm.wps.samples.HelloWorld, would be stored in /WEB-INF/classes/com/ibm/wps/samples/HelloWorld.class .

/META-INF

Location for the manifest file, manifest.mf. The manifest is in the standard JAR file format as defined by the Java 1.3 specification. The contents of the /META-INF directory is not served to clients.

Support for multiple markups and locales

Portal aggregation allows you to package JSPs to support multiple markups, clients, and locales. JSPs that contain mostly text, such as help JSPs, can be translated directly rather than storing strings in a resource bundle. For JSPs that do not use resource bundles, store it in the appropriate localized location. When a portlet uses a JSP for rendering of the portlet's content, the portal searches for and selects the proper JSP based on the client type (including browser), markup language, and locale indicated in the request. To include a JSP in a portlet use the function PortletContext.include():


   getPortletConfig().getContext().include(jsp_path/jspname.jsp, portletRequest, portletResponse);

To support multiple markup types and locales, the portlet's JSP's must be packaged in the WAR file using the following directory structure:

jsp_path/markup_type /language _country/client/jspname.jsp

Where

jsp_path

a path defined by the developer. For example, JSPs can be located in the root of the WAR file or in a jsp directory. However, this path must not include mime-type/language_country_variant . The include() method already locates the correct JSP also in those directories.

markup_type

is either html, wml, or chtml .

language

is the language for this JSP, for example, en, ja, or de .

country

is the country of the JSP, for example, US, UK, or CA.

client

is the type of device. For example, it could indicate a JSP with browser-specific markup, such as ie or ns4. Manage Clients Help describes how clients are identified by the portal server.

For example, if the client is using Internet Explorer 5 with language.properties are set to English (United States), the method include(/mypath/mytemplate.jsp, portletRequest, portletResponse) would cause the portal server to look for the JSP in the following order.

  1. /html/mypath/ie5/en_US/mytemplate.jsp
  2. /html/mypath/ie5/en/mytemplate.jsp
  3. /html/mypath/ie5/mytemplate.jsp
  4. /html/mypath/en_US/mytemplate.jsp
  5. /html/mypath/en/mytemplate.jsp
  6. /html/mypath/mytemplate.jsp
  7. /html/en_US/mytemplate.jsp
  8. /html/en/mytemplate.jsp
  9. /html/mytemplate.jsp
  10. /mytemplate.jsp

 

Package a portlet and resources into a WAR file

Any JAR utility may be used to build a WAR file. Below are examples of how to use the JAR utility provided by WebSphere Application Server.

After the WAR file is created, it can be installed to WebSphere Portal as described in portlet administration .

To facilitate deployment of portlet applications and complex portlets, provide a portlet configuration file that can be invoked by the XML configuration interface. The XML configuration interface allows the portlet developer to specify places, pages, themes, skins, supported markups and clients, and other settings for a portlet application. This is especially useful for portlets that use messaging because these portlets have to be placed on the same page. For more information see Representation of a portal configuration in XML.

 

Generating markup

In the first example, the portlet provided markup by using a Java PrintWriter. Most markup is generated using JSPs. An exception to this is when the portlet has to transform XML source. In this case, the portlet can use XSLT to generate markup.

 

Using JSPs to generate markup

One of the easiest ways to separate the portlet markup from the main functionality of the portlet is to use a JSP. Below is the JSP for the edit page of the Hello World2 sample. A separate view or help JSP would exist to provide the user interface for supporting the additional portlet modes.

There are several points to keep in mind when writing your JSPs:

  1. For consistency in portal look and feel, use the portlet's class specifications in the portlet's style sheet .

  2. Include the JSP tag library for WebSphere Portal to obtain the needed functionality for managing your portlet's namespace. Also, use the <portletAPI:encodeNamespace> tag to set the request parameter names.

  3. In this example, Java String objects are passed to the JSP from the portlet's doEdit() method. For more complex data passing, it may be necessary to create a separate object (a data view bean or a hash table) to contain the data needed for display.

Example: JSP for edit mode of Hello World2



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

<jsp:useBean id="saveURI" class="java.lang.String" scope="request" /> 
<jsp:useBean id="cancelURI" class="java.lang.String" scope="request" /> 
<jsp:useBean id="userName" class="java.lang.String" scope="request" /> 

<!-- build table for edit screen --> 
<div CLASS="wpsEditBack"> 
<span class="wpsEditHead">Configure Hello World Portlet</SPAN> <br> 
<form method="post" name="form" action="<%= saveURI %>"> 
<table WIDTH="100%" CELLSPACING="0" CELLPADDING="0" BORDER="0"> 
   <tr> 
     <td align="right" class="wpsEditText">Enter string to display:</TD> 
     <td><input class="wpsEditField" size="20" type="text" 
                name="<%=portletResponse.encodeNamespace("userName")%>" 
                value=<%= userName %>>
     </td> 
   </tr> 
  <!-- Empty row --> 
  <tr>
    <td> </td>
  </tr> 
  <tr>
    <td class="wpsButtonText"> 
      <input type="submit" name="save" value="Save" > 
      <input type="button" value="Cancel" 
             onClick="window.location.href='<%= cancelURI %>'" > 
    </td> 
  </tr> 
</table>
</form> 
</div> 


 

Generating markup for multiple devices

One of the main features of the WebSphere Portal is its support for multiple device capabilities. WebSphere Portal supports PC browsers, i-mode and WAP phones, and future versions of the product are likely to support additional device types. The challenge with supporting multiple devices is to render content differently depending on the characteristics of the browser. One browser may accept HTML 4.0, another WML, and one WAP phone may have four lines with twenty-five characters while another phone has its own PDA-style interface.

The following example demonstrates a way to check for the markup type associated with the current device prior to generating the portlet's markup. The doMarkupOutput() method is provided and called to handle requests for output for each portlet mode. The Client object obtained from the PortletRequest identifies the markup language required for the current device.

Example: Adding a method to handle different markup



...
{
  public void init (PortletConfig portletConfig) throws UnavailableException
  {
    super.init( portletConfig );  // Call super to do further init
  }

  public void doView( PortletRequest request, PortletResponse response )
         throws PortletException, IOException
  {
    doMarkupOutput( request, response, "View" );
  }

  public void doHelp( PortletRequest request, PortletResponse response )
         throws PortletException, IOException
  {
    doMarkupOutput( request, response, "Help" );
  }

  public void doEdit( PortletRequest request, PortletResponse response )
         throws PortletException, IOException
  {
    doMarkupOutput( request, response, "Edit" );
  }

  public void doMarkupOutput( PortletRequest request, 
                    PortletResponse response,
                    String portletModeString ) throws PortletException, IOException
  {
    String   markup = request.getClient().getMarkupName();

    PrintWriter writer = response.getWriter();

    //-------------------------------------------------------------------------------------
    // Check the client device to determine the type of markup to generate
    //-------------------------------------------------------------------------------------
    if( markup.equalsIgnoreCase("HTML") )          // Standard HTML: text/html
    {
      writer.println( "<p>Hello Portal... The Portlet Mode is: "+portletModeString+" </p>" );
    }
    else if( markup.equalsIgnoreCase("WML") )      // WML: text/wml
    {
      writer.println( "<card id=\"hello\"><P>Hello Portal... " );
      writer.println( "The Portlet Mode is: "+portletModeString+" </P></card>" );
    }
    else if( markup.equalsIgnoreCase("CHTML") )    // Compact HTML: text/html
    {
      writer.println( "<P>Hello Portal... The Portlet Mode is: "+portletModeString+" </P>" );
    }    
    else // Unrecognized Markup Type Error: Throw An Exception
    {
      throw( new PortletException( "Unknown Markup Type") );
    }
  }
}


 

Using the MVCPortlet class

WebSphere Portal provides an MVCPortlet class as part of the com.ibm.wps.portlets package. Portlets that extend this class do not have to check the client markup before sending output. Instead, they provide controller classes for each markup type. The servlet class tag in the Web application descriptor defines an empty stub class that extends MVCPortlet. The controller classes are identified as initialization parameters as shown in the following example.

  <servlet-class>com.mycompany.myportlet.myMVCportlet</servlet-class>
  <init-param>
    <param-name>controller.html</param-name>
    <param-value>com.mycompany.myportlet.HTMLController</param-value>
  </init-param>
  <init-param>
    <param-name>controller.wml</param-name>
    <param-value>com.mycompany.myportlet.WMLController</param-value>
  </init-param>
  <init-param>
    <param-name>controller.chtml</param-name>
    <param-value>com.mycompany.myportlet.CHTMLController</param-value>
  </init-param>

The controller classes extend AbstractPortletController and contain the portlet code, such as the doView(), doEdit() methods or, if necessary, an ActionListener. Application Developer allows you to create an MVCPortlet from the Create a Portlet project wizard.

 

Using persistence

Portlet data is saved, retrieved, or deleted to persistence using the PortletData object. Portlets can store values in the PortletData object only when the portlet is in edit mode. If the portlet is on a group page, then information saved in PortletData is available to all users of the portlet. The portlet retrieves a reference to an instance of PortletData by calling the getData() method of the PortletRequest object.

In the following example from BookmarkPortlet.java from bookmark6.war, after the user enters a URL to bookmark on the edit page, the setAttribute() and store() methods save the information to PortletData. First, however, the user's URL_COUNT is retrieved from PortletData so that it can be updated.


   PortletData data = event.getRequest().getData();
   String count = (String) data.getAttribute(URL_COUNT);
   int i = 0;

   if (count != null)
    {
       i = Integer.parseInt(count);
    }
    i++;

    data.setAttribute(NAME_PREFIX + i, name);
    data.setAttribute(URL_PREFIX  + i, url);
    data.setAttribute(URL_COUNT      , Integer.toString(i));

    try
    {
       data.store();
    }
    catch (IOException e)
    {
    throw new PortletException (e);
    }
         

Only data of the Java String type may be saved in the PortletData object.

Note: A portlet located on the default portal page (prior to the user logging in) does not have access to PortletData. To prevent unwanted errors on the default page, either inform administrators not to include this portlet on the default page or test the availability of PortletData before trying to access its attributes.

PortletData is also used in the Hello World2 sample to allow the user to edit the greeting and save it to persistence. In addition, the PortletContext is used to invoke a JSP for rendering the view and edit markup.

  1. The default greeting to display is obtained from the Web application deployment descriptor . The following shows this descriptor with the initialization parameter defaultHelloString set to "Hello!".

    Example: Web application descriptor for Hello World2


    
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE web-app
        PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
               "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
       <web-app id="WebApp_3">
          <display-name>HelloPortlet</display-name>
          <servlet id="Servlet_1">
             <servlet-name>Hello World2</servlet-name>
             <servlet-class>com.ibm.wps.samplets.helloworld.HelloWorld2</servlet-class>
              <init-param>
                  <param-name>defaultHelloString</param-name>
                  <param-value>Hello!</param-value>
              </init-param>         
          </servlet>      
          <servlet-mapping id="ServletMapping_1">
             <servlet-name>Hello World2</servlet-name>
             <url-pattern>/Hello World2/*</url-pattern>
          </servlet-mapping>
       </web-app>
      
    

    Configuration data that is set by the <init-param> tags are read-only and maintained for all users and every concrete portlet derived from the portlet. To allow different configurations for each concrete portlet, then the data should be set in the <concrete-portlet> tag of the portlet deployment descriptor.

  2. The doView() method receives control prior to the standard display for this portlet. The PortletData object is accessed to obtain the string to display. If the user has not yet specified a string to display, the default string will be used. The string is stored in the PortletRequest object to make it available to the JSP which generates the view markup for this portlet (viewJSP).

  3. The doEdit() method receives control prior to the display of the edit page for this portlet. A return URI is created and passed to the JSP for edit mode using the PortletRequest object. The save action is included in the return URI to result in an invocation of the ActionListener for this portlet. The portal passes control to the ActionListener upon processing the save action. The ActionListener can preserve the user entered "edit" information in the persistent storage. See Action events for more information about ActionListener and portlet actions.

    Code for the HelloActionListener is shown following the portlet code.

Example: Saving and retrieving data from PortletConfig



package com.ibm.wps.samplets.helloworld;
import org.apache.jetspeed.portlet.DefaultPortletAction;
import org.apache.jetspeed.portlet.*;
import org.apache.jetspeed.portlet.event.*;
import org.apache.jetspeed.portlets.*;
import java.io.*;
import java.util.*;

public class HelloWorld2 extends AbstractPortlet implements ActionListener{
    // Since there is a single instance of the portlet, only use instance variables
    // for immutable values that are the same for all users of the portlet
    private final static String viewJSP = "/WEB-INF/helloworld/html/HelloWorldView.jsp";
    private final static String editJSP = "/WEB-INF/helloworld/html/HelloWorldEdit.jsp";
    private String defaultString;

    public void init (PortletConfig portletConfig) throws UnavailableException
    {

        super.init( portletConfig );
        if ( getPortletLog().isDebugEnabled() ) {
            getPortletLog().debug("HelloWorld: init called");
        }
        // The default Hello String is obtained from the portlet configuration parameters
        defaultString = portletConfig.getInitParameter("defaultHelloString");
    }

    public void doView (PortletRequest request, PortletResponse response)
    throws PortletException, IOException
    {
        //Get the user's name to display from persistent storage
        PortletData portletData = request.getData();
        String stringToDisplay = (String) portletData.getAttribute("userName");

        // If this is the first time the user has accessed this portlet, then
        // no display string will be found for this user in persistent storage
        if (stringToDisplay == null) {
            stringToDisplay = defaultString;    // set default string
        }

        // Add the display string to the portlet request to make it accessible by the view JSP
        request.setAttribute("userName", stringToDisplay);

        // Get a context for the current session for invoking the JSP
        PortletContext context = getPortletConfig().getContext();
        context.include(viewJSP, request, response);

    }

    public void doEdit(PortletRequest portletRequest, PortletResponse portletResponse )
    throws PortletException, IOException
    {

        // Create the return URI for the edit page
        PortletURI returnURI = portletResponse.createReturnURI();

        // Preserve the Cancel URI in the request to make it accessible by the edit JSP
        portletRequest.setAttribute("cancelURI", returnURI.toString());

        // For the "Save" button the return URI must include the "Save" action
        // so the Action Listener for this portlet will be invoked
        PortletAction saveAction = new DefaultPortletAction("save");
        returnURI.addAction(saveAction);

        // Preserve the Save URI in the request to make it accessible by the edit JSP
        portletRequest.setAttribute("saveURI", returnURI.toString());

        //Get the user's name to display from persistent storage
        String stringToDisplay = (String)portletRequest.getData().getAttribute("userName");
        if (stringToDisplay == null) {
            stringToDisplay = defaultString;    // none found, set default string
        }

        // Add the display string to the request to make it accessible by the edit JSP
        // as an inital value of the input field on the edit form
        portletRequest.setAttribute("userName", stringToDisplay);

       // Get a context for the current session for invoking the JSP
        PortletContext context = getPortletConfig().getContext();
        context.include(editJSP, portletRequest, portletResponse);
    }

  public void actionPerformed(ActionEvent event) {
    DefaultPortletAction action = (DefaultPortletAction)event.getAction();
    HelloWorld2 helloPortlet = (HelloWorld2)event.getPortlet();
    PortletLog log = helloPortlet.getPortletLog();

    // If this is a save action, then see if the user specified a name
    if ( action!=null ) {
      if ( action.getName().equals("save") ) {

        PortletRequest request = event.getRequest();
        PortletData portData = request.getData();
        String userName = request.getParameter("userName");
        try {
          // Save the name specified by the user
          if ( userName != null ) {

            portData.setAttribute("userName", userName);
            portData.store();
          }
        } catch ( AccessDeniedException ade ) {
        } catch ( IOException ioe ) {
          log.error( "<i><b>Couldn't write the user date to persistence " );
          log.error( "because an I/O Error occurred.</b></i>" );
        }
      }

    }
  }
}



As mentioned previously, an ActionListener is implemented by Hello World2 to process the save action. The user enters a name on the edit page and the actionPerformed() method of the ActionListener obtains the user-specified string from the PortletRequest object for storing in the user's persistent storage. The ActionListener is invoked prior to returning to the doView() method of the portlet, thus if the user failed to enter a name, the ActionListener can force the portlet to remain in edit mode, waiting for input from the user.

 

Portlet messaging

The Portlet API supports messaging between portlets on a page. For example, if four portlets (Left, Right, Top, Bottom) are part of a portlet application called Sides, then the portlet Left can send information to portlet Right, Top, and Bottom as long as they are on the user's same page. The following conditions apply to portlets that send and receive messages.

Typically a message is sent from a portlet's action listener and received by another portlet's message listener. The user performs an action in one portlet. The action event is captured and processed. Based on the results of that action, the portlet can send a message to other portlets using the send() method of the PortletContext object.

Example: ActionListener that sends a message



...

public void actionPerformed (ActionEvent event) {

...

   if (action.getName().equals("browse")) {
      log.debug("BookmarkActionListener - browse action");
      String url = (String) action.getParameters().get("url");
      log.debug("BookmarkPortletActionListener - opening link: " + url);
...
      try {
         portlet.getConfig().getContext().send(null, new DefaultPortletMessage(url));
      }
      catch (AccessDeniedException ade) {
         log.error("BookmarkPortletActionListener - unable to send message.");
         log.error("URL = " + url + " - AccessDenied");
      }
   }

...

}

In this sample, the "browse" action has been defined in the bookmark portlet.


   DefaultPortletAction browseAction = new org.apache.jetspeed.portlets.DefaultPortletAction("browse");
   browseAction.addParameter("url", url);
   PortletURI portletURI = response.createReturnURI();
   portletURI.addAction(browseAction);
   String actionURI = portletURI.toString();
   

The send() method takes the following arguments :

portletName

The name of the portlet that receives this request. In the example above, the message is sent to null, which means it is broadcast to all portlet's in the same portlet application. To send the message to a specific portlet, specify the name of the portlet as it is defined by the <portlet-name> tag in the portlet deployment descriptor.

message

The message to be sent. The message must be a PortletMessage object or any subclass that implements that interface. In the example above, the message is instantiated in a DefaultPortletMessage object containing the url string from the action that was performed.

The receiving portlet has a message listener that retrieves the message using the getMessage() method of the message event.

Example: MessageListener that receives a message



    public void messageReceived (MessageEvent event) 
    throws PortletException
    {
        PortletMessage msg = event.getMessage();

        if (msg instanceof DefaultPortletMessage) 
        {
            String url = ((DefaultPortletMessage)msg).getText();

            PortletAdapter portlet = (PortletAdapter)event.getPortlet();

            portlet.getPortletLog().debug("BookmarkPortletMessageListener messageReceived");

            PortletRequest request = event.getRequest();

            PortletSession session = request.getSession();

            session.setAttribute("url",url); 
        }
    }
}


Since the MessageListener set the received message as an attribute of the session, the receiving portlet must get the "url" parameter from the session.

For more information about action events and message events, see Portlet events .

 

Message and trace logging

Portlets can write message and trace information to log files, which are maintained in the wp_root/log/ directory. The log files help the portal administrator investigate portlet errors and special conditions and help the portlet developer test and debug portlets. The Portlet API provides the PortletLog class, which has methods to write message and trace information to the logs.

debug()

Writes trace information to wps_[timestamp].log .

info()

Writes informational messages to wps_[timestamp].log.

error()

Writes error messages to wps_[timestamp].log .

warn()

Writes warning messages to wps_[timestamp].log .

[timestamp] has the following format:

   year.month.date-hour.minute.second 
   

For example, wps_2002.03.08-14.00.00.log was written on March 8, 2002, at 2:00 pm.

If you access the portlet log multiple times in a method it is good idea to assign the log reference to a variable, for example:


  private PortletLog myLogRef = getPortletLog(); 

Since logging operations are expensive, PortletLog provides methods to determine if logging is enabled for a given level. Your portlets should write to the log of a given level only if the log is tracking messages of that level. For example:


  if( getPortletLog().isDebugEnabled() )
  {
    myLogRef.debug("Warning, Portlet Resources are low!");
  }
  

For more information about logging in WebSphere Portal, see Logging management .

 

Refresh the portlet cache

The portlet cache holds the complete output of the portlet. As a result, the portal server does not call the portlet's service() or doView() methods when the user changes the portlet state. The getLastModified() method enables the portlet developer to inform the container when the current cache entry for the portlet should be invalidated, and therefore the portlet's content should be refreshed. You can use WindowListeners to set a new timestamp and then return the timestamp in getLastModified. The following examples show parts of a bookmark portlet that caches its output, but needs to change its content immediately if the window state changes to provide additional output.

First, in the portlet deployment descriptor, the WindowListener and supported portlet states are registered and caching is enabled.

getLastModified() example: Portlet deployment descriptor



  <listener>
    <listener-class type="window">
      com.mycompany.portlets.bookmark.BookmarkPortletWindowListener
    </listener-class>
  </listener>
  <cache>
    <expires>-1</expires>
    <shared>NO</shared>
  </cache>
  <allows>
    <maximized/>
    <minimized/>
  </allows>


Next, the WindowListener sets the timestamp to the LAST_MODIFIED attribute to the portlet's session.

getLastModified() example: WindowListener



package com.mycompany.portlets.bookmark;

import org.apache.jetspeed.portlets.*;
import org.apache.jetspeed.portlet.*;
Import org.apache.jetspeed.portlet.event.*;
Import java.io.IOException;                     //Java stuff

public class BookmarkPortletWindowListener extends WindowAdapter {

  public void windowMaximized (WindowEvent event) throws PortletException
  {
    setLastModified(event);
  }

  public void windowRestored (WindowEvent event) throws PortletException
  {
    setLastModified(event);
  }

  private void setLastModified(WindowEvent event) {
    PortletSession session = event.getRequest().getSession(false);
    if (session != null) {
      session.setAttribute(BookmarkPortlet.LAST_MODIFIED, new Long(System.currentTimeMillis()));
    }
  }

  public void windowDetached (WindowEvent event) throws PortletException
  {
  }
}


Finally, the portlet's getLastModified() method returns the timestamp when the request is made.

getLastModified() example: BookmarkPortlet



    public long getLastModified(PortletRequest request) {
        PortletSession session = request.getSession(false);

        if (session != null) {
            Long lastModified = (Long) session.getAttribute(LAST_MODIFIED);
            if (lastModified != null) {
                return lastModified.longValue();
            }
        }
        return -1;
    }


 

Parallel portlet rendering

By default, the portal server renders portlets to a page in parallel (using separate processing threads). The administrator can set the number of threads used for portlet rendering or turn this capability off, causing portlets to be rendered serially using a single thread. These settings are made in the JetspeedResources.properties file. See Parallel portlet rendering under Administering portals for more information.

Portlet rendering is also set at the portlet level, which by default is turned off. To set parallel portlet rendering on for a portlet, set this configuration parameter in the <concrete-portlet> tag of the portlet deployment descriptor.

      <config-param>
         <param-name>parallel</param-name>
         <param-value>true</param-value>
      </config-param> 

Set this parameter in the portlet.xml helps the administrator avoid having to make this setting after deploying the portlet.

 

Personalize the portlet

WebSphere Personalization enables customers to easily build Web sites that match site content to site visitors. Whereas customization allows users to set their own preferences or determine what content to see, personalization is used by the portal provider (developers, administrators) to determine what content is displayed based on properties of the user.

All personalization solutions have a concept of users, content, and a matching technology. WebSphere Portal provides the user concept, implemented as a User class with a User Profile. Content is specific to each installation of a portal, and can include customer data stores, legacy databases, subscription content providers, and others. For a matching technology, WebSphere Personalization provides a resource engine and rules engine that enable rules-based personalization. Together, these components allow you to develop portlets that use personalization rules to match content to users.

Consider an example of an intranet portal that provides a portlet for purchasing office supplies. Orders of over fifty dollars have to be approved by a manager. The portlet needs show a button that launches an approval form only if the portlet user is a manager. In this case, the button can added to the portlet's view JSP as a content spot. A content spot is a place on your web page where you want to display personalized content. Then a rule is created that maps that spot to the user of the portlet, for example:

  when the user is a manager, show approval button

WebSphere Studio Application Developer V4.0.3 is used to create content spots in portlet JSPs. The Personalization Workspace is used to develop rules that display content in the spot based on properties of the user. WebSphere Portal includes a Sample Personalization Portlet that demonstrates how a personalization rule can be included within a portlet's JSP to determine the markup to be displayed. This sample serves as an introduction to the concepts of personalization. The specific type of rule demonstrated is a classifier rule. The rule differentiates portal users based on the "Interests" attribute in the portal User Profile. The JSP invokes personalization to classify the current user, and then based on the result, generates markup to display one of four possible graphics.

The following sections describe how to include personalization in the sample portlet. The portlet code and resources can be found in the wp_root/dev/SampPers directory of the portal server.

 

WebSphere Portal User Class

WebSphere Portal provides the following classes that implement the required interfaces to enable access to the portal User Profile from personalization. As part of implementing these interfaces, appropriate getter and setter methods are provided for all of the attributes of a user that are kept in the User Profile.

The User class implements two kinds of user attributes: fixed attributes and dynamic attributes. Both kinds are persistent (stored in back end databases), and both kinds can be used for personalization. The difference between the two types primarily has to do with what's involved in adding new attributes.

The tradeoff is in performance and efficiency. There is a runtime advantage to using fixed attributes and shorter development and administration time advantages to using dynamic attributes. The User class provides both a small set of fixed attributes and the capability of adding and using dynamic attributes.

 

Objectives of the Sample

The sign up page of the WebSphere Portal contains both fixed and dynamic attributes. First name, Last name, E-mail address, and Preferred language are fixed attributes; Interests is a dynamic attribute. It is the dynamic Interests attribute that is accessed by this sample portlet. There are four possible values for Interests: Music, Politics, Sports, and -nothing selected- . This example uses a personalization rule to determine the classification of the current portal user, based on the setting of this attribute, and uses this classification to determine which of four possible images to present.

The capabilities of WebSphere Personalization go well beyond those shown in this sample. In addition to User Resources, personalization can access Content Resources as well. A classifier rule is just one of the three types of personalization rules. You can also create action rules, to allow personalization to select data or content, thus moving the decision making process outside of the portlet; and binding rules, to combine actions and classifiers, to specify what actions to perform when your defined conditions are encountered. Consult the WebSphere Personalization documentation for further information.

This sample is meant to be a learning exercise. The sample portlet is provided without personalization. The steps below describe what do to include personalization into the portlet. Instructions are provided for you to:

  1. Import the portlet war file into the WebSphere Studio Application Developer using the Portlet Application Wizard.

  2. Use the Personalization Wizard in the application developer to create a content spot. A content spot is a section of your Web page where a rule is invoked to personalize content.

  3. Add the content spot to the portlet's view JSP.

  4. Use the Personalization Workspace to:
    • Create a project for managing the personalization resources and rules.
    • Create a classifier rule.
    • Associate the classifier rule with the content spot on the portlet's JSP.

 

Prerequisite Software

In order to follow the steps outlined here have the following products installed:

 

Files Included with the Sample

The files related to this sample can be found in the <wp_root>\dev\SampPers\ directory.

File Contents
<wp_root>\dev\SampPers\war\SampPersPortlet.war The sample personalization portlet (including source files). The instructions included here are used to add personalization to this portlet.
<wp_root>\dev\SampPers\resources\User.hrf The XML file identifying the portal user resource. Instructions are given for making this file available to Personalization.
<wp_root>\dev\SampPers\solution\SampPersPortlet_Solution.war

<wp_root>\dev\SampPers\solution\ClassifyByInterest.clf

If you have any questions in walking through this sample, you may find it helpful to view the files in the solution subdirectory. The sample portlet, modified to include personalization, and the XML file for the classification rule are included.

 

Import the SampPersPortlet.war file

To use the Personalization Content Spot wizard import the <wp_root>\dev\SampPers\war\SampPersPortlet.war file into WebSphere Studio Application Developer.

  1. Open the application developer and click File then click New then click Project to create a new project.

  2. Select Portlet Development and Portlet application project in the New Project dialog, and click Next.

  3. Enter a name for your project, check the box titled Use default location, and click Next.

  4. Select None for portlet selection and click Finish. You will be importing an already existing portlet into the application developer. The wizard automatically includes the necessary jar files in the project class path needed to compile the portlet. This wizard can also be used to create a new portlet. Consult the Portlet Application wizard documentation for further information on creating new portlets.

  5. Highlight the newly created project in the application developer navigator pane, and click File then click Import.

  6. Select WAR file in the Select dialog and click Next.

  7. Use the Browse button to locate the <wp_root>\dev\SampPers\war\SampPersPortlet.war file. Click Finish. When prompted to overwrite the portlet.xml file click Yes To All.

  8. Verify that no errors are listed in the Tasks View.

    Note: If you have errors indicating there is an "Incorrect servlet reference for the portlet" and "Need to assign servlet for the portlet", then select the project in the Navigator pane, right-click to display a pop-up menu, and click Run validation .

 

Create a ContentSpot

  1. Select the <project_name>\source\com\ibm\wps\portlets\samppers directory for your project in the navigator pane of the application developer.

  2. Click the Content Spot Wizard icon on the menu bar.

  3. Click Next on the Content Spot Wizard welcome page.

  4. Specify a name of UserTypeSpot for the Content Spot Java Bean.

  5. Select None as the return type for the rule. Classifier rules have a return type of none. If you were creating a content spot for returning content then you would have to import the .hrf file for that resource into your portlet application and select that resource as the return type for your Content Spot. Consult the personalization documentation for further information on resource creation and returning resources from rules. Click Next.

  6. The Finish page displays the name of the file that will be created by the wizard for your content spot. The file will be created in the com.ibm.wps.portlets.samppers package. Click Finish.

 

Add the Content Spot to the SamplePersView.jsp

  1. Double click on the SamplePersView.jsp located in the <project_name>\webApplication\JSP\html directory of your portlet application in the application developer to open the JSP. This is the portlet's view mode JSP. Make sure the Design View of the JSP is displayed. If not, then click on the Design tab below the current view.

  2. Expand the <project_name>\webApplication\WEB-INF\classes\com\ibm\wps\portlets\samppers directory to locate the file UserTypeSpot.class. To add the content spot to the JSP, drag this content spot class to the JSP, placing it at the top of the design view window. An Attributes window will open.

  3. Click OK to close the Attributes window.

  4. Open the Source View of the JSP. Locate the jsp:useBean tag created for the content spot. It will look like:
    	<jsp:useBean class=com.ibm.wps.portlets.samppers.UserTypeSpot id=userTypeSpot /> 
    	

Type the following line immediately after the statement with the jsp:useBean:

	<% userTypeSpot.setRequest(request); %>
	

This statement makes the HttpServletRequest object available to the personalization rule processor. The completed content spot should then appear as follows:

	<jsp:useBean class=com.ibm.wps.portlets.samppers.UserTypeSpot id=userTypeSpot />
	<% userTypeSpot.setRequest(request); %>
	

  • Since this content spot will eventually use a classifier rule (which is yet to be created), add code to determine the result of the classification. There are four possible classifications of user: musical, athletic, political, or none of these. Based upon which classification is returned, a graphic will be displayed that matches the classification. For instance, if the user is classified as musical, then a graphic displaying musical notes is shown. Look within the JSP for a table containing the following:
    	<TD><IMG src="<%=response.encodeURL("images/samppers/music.gif")%>" 
                width="96"  border="0"></TD>
    
    <TD><IMG src="<%=response.encodeURL("images/samppers/sports.gif")%>" border="0"></TD>
    <TD><IMG src="<%=response.encodeURL("images/samppers/politics.gif")%>" width="34" border="0"></TD>
    <TD><IMG src="<%=response.encodeURL("images/samppers/computer.gif")%>" width="34" border="0"></TD>

    Only one of these graphics should be displayed as determined by the user's classification. Type the following if-then-else lines shown below to add this logic surrounding the table.

          <% if (userTypeSpot.isClassifiedAs("musical")) {%>
             <TD><IMG src="<%=response.encodeURL("images/samppers/music.gif")%>" 
                     width="96"  border="0"></TD>
          <% } else { 
                if (userTypeSpot.isClassifiedAs("athletic")) {%>
                 <TD><IMG src="<%=response.encodeURL("images/samppers/sports.gif")%>" 
                           border="0"></TD>
          <%   } else {  
                if (userTypeSpot.isClassifiedAs("political")) {%>
                 <TD><IMG src="<%=response.encodeURL("images/samppers/politics.gif")%>" 
                         width="34"  border="0"></TD>
          <%   } else {%>
    	       <TD><IMG src="<%=response.encodeURL("images/samppers/computer.gif")%>" 
                       width="34"  border="0"></TD>	          
          <%   }	  
              }
             } %>  
    
    	

    Note: In the JSP, the <jsp:usebean> for the UserTypeSpot must be located above its usage in the <table>. For maintainability, you may want to move it to be near the other <jsp:usebean> tags in the file.

  • Save the JSP.

  • Highlight the portlet application project in the navigator pane and click the right mouse button to present a popup menu. Select Export WAR.

  • On the WAR Export dialog, click Browse to locate a directory for saving the portlet war file. The directory you select must be accessible when the portlet is later installed. Save the war file as SampPersPortlet.war

  • Click Finish to generate the war file.

  • You now need to export the UserTypeSpot.class file to a location on the server where Personalization is installed. Highlight the portlet application project in the navigator pane again, and click File then click Export from the menu bar.

  • Select File system and click Next.

  • Navigate to...

    <project_name>\webApplication\WEB-INF\classes\com\ibm\wps\portlets\samppers

    ...and select UserTypeSpot.class in the selection box. Click Browse... to locate a directory that is accessible to the WebSphere Application Server where Personalization is installed. In later steps, it is assumed the name of this directory is C:\PZNPortlet; to help you to follow these instructions you may want to use this directory name. Check the box Create directory structure for files. Click Finish.

     

    Install SamplePZNPortlet.war

    1. Log in to the WebSphere Portal with an administrator user ID.

    2. Use the Install Portlets portlet on the Portlets page of Portal Administration to install the SampPersPortlet.war file.

    3. Use the Access Control Administration portlet on the Security page of Portal Administration to give SamplePersonalizationPortlet view access to All Authenticated Users.

    4. Use the Edit Layout and Content task in the Work with Pages page group to add the Sample Personalization Portlet to a page that is accessible by all users. (This will make it easier for testing the portlet later on.)

     

    Create a Personalization Project

    1. In order to create a classifier rule that accesses the portal User Profile, copy the portal user XML resource file to a location that is accessible by the server where Personalization is installed. Copy file User.hrf from the...

      <wp_root>\dev\SampPers\resources

      ...directory to...

      C:\PZNPortlet\resources

      This is the same root directory where you previously exported the UserTypeSpot.class.

      Note that the User.hrf file shipped with this sample is based on the base User profile shipped with WebSphere Portal. It can be used as is by other portlets using personalization. If you add any dynamic attributes to the portal User Profile create a new User.hrf file. Consult the Personalization documentation for instructions on creating this file.

    2. From a browser, open the Personalization Workspace. For example:

      http://your_hostname/wps/PersWorkspace/index.jsp

      ...and log into the workspace with a user ID having the developer role.

    3. Click Global Settings to open the Personalization Global Settings dialog.

    4. Click the icon to add a new project. The Add Project dialog opens.

    5. Enter a path for your project along with a project name. The path you specify is located on the machine where the WebSphere Application Server with Personalization is installed. For convenience, use the same directory where you copied the User.hrf and the UserTypeSpot.class files, for example, C:\PZNPortlet. Click OK

    6. The Resource Path identifies the directory containing the resource XML file (.hrf files). Enter C:\PZNPortlet\resources as the resource path. The Resource and Content Spot Class Path identifies the location of compiled resources and content spots. Enter...

      C:\PZNPortlet\webApplication\WEB-INF\classes

      ...as the path. (In both cases substitute C:\PZNPortlet with the directory you used previously for the content spot class and the user resource). Click Save to save the project.

    7. If the project is not the current project in the Manage Projects list box, then select the project and click the Make Current button.

     

    Create a Personalization Rule

    The next task is to use the Personalization Workspace to create the classification rule to be accessed by the sample portlet's view JSP to determine the content to display based on the user's attributes.

    1. In the Rule Composer of the Personalization Workspace, be sure Classifiers is selected in the drop down selection box.

    2. Click the icon to create a new classfier rule. The Add Classfier dialog is displayed.

    3. Enter ClassifierByInterest for the name of the classfiier. You can also optionally add a comment.

    4. Click Classification in the Classifier Description area to open up a window allowing you to enter a classification name of musical. Click Save in the Classification Name popup window.

    5. Click Resource.Attribute in the Classifier Description area to open the Select a Resource and its Attribute dialog. Click the radio button for current User as the Resource. The current User selection appears because you made the User.hrf resource file available to the Personalization Workspace. Select the Interest attribute in the scrolling list on the right. Leave the attribute's type as text. Click Save to close the dialog.

    6. Click value in the Classifier Description area to open a window for specifying a value of type Text. Enter music as the value and click Save.

    7. Click add Classification in the Classifier Description area two more times, repeating the previous 3 steps to create a rule that looks like:

      Classifier description (click an underlined value to edit it)

          ClassifierByInterest is
          musical when
             current User.Interest is equal to music
                 add condition
          athletic when
             current User.Interest is equal to sport
                 add condition
          political when
             current User.Interest is equal to politics
                 add condition
              add Classification
              Otherwise Classification

      There is no need to addConditions for this sample nor to provide an Otherwise classification.

    8. Click Save to save the rule.

     

    Associate the Rule with the Content Spot

    1. Open the Campaign Manager of the Personalization Workspace.

    2. Click Normal View to display the content spots and their associated rules. The Rule for the UserTypeSpot is a link titled empty.

    3. Click on empty to display a dialog where you can select a rule to fill the content spot.

    4. Select ClassifyByInterest and click OK. You have now associated a rule with the content spot.

     

    Publish the Rule

    1. From within the Personalization Workspace, click Global Settings to open the Personalization Global Settings dialog.

    2. Click Manage Publish Servers to display a listbox of publish servers.

    3. Click the icon to Add Publish Server. Type a name for the server where you have Personalization installed and a URL to get to that server (for example: http://cayuga.endicott.ibm.com). If the server requires authentication, select the Requires authentication check box, and enter a user ID and password to access the server. Click Save.

    4. Click Publish files.

    5. Select the publishing server, and Publish All. Click Publish.

     

    Publish the Portal User Resource

    You need make the portal user resource file (User.hrf) available to the personalization runtime. This step need only be performed once.

    From a browser window, enter:

    http://servername/PersAdmin_path/ImportServlet?filename=path_to_User.hrf
    

    For example, if the full path to the resource file is...

    C:\PZNPortlet\resources\User.hrf

    The Context path for Personalization Administration is /wps/PersAdmin; and, the server name is cayuga.endicott.ibm.com, then the following url is used (all on one line).

    http://cayuga.endicott.ibm.com/wps/PersAdmin/ImportServlet?
                              filename=C:\PZNPortlet\resources\User.hrf
    

    Upon successful publication of the user resource, it is now available to all portlet applications.

     

    Testing the Portlet

    Create several portal users, selecting a different interest for each on the sign up page. The graphic displayed by the portlet will be dependent on the interest you select. For example, for a user named Jimi Hendrix having an interest of music, the portlet output appears as follows.



    Sample Personalization Portlet
    Welcome to the Sample Personalization Portlet  
    Current user profile settings:  
    User ID: Jimi  
    First name: Jimi  
    Last name: Hendrix  
    Interests: Music  
    The graphic to the left was chosen for you based on your user profile setting labeled Interests:

    See also

     

    WebSphere is a trademark of the IBM Corporation in the United States, other countries, or both.

     

    IBM is a trademark of the IBM Corporation in the United States, other countries, or both.