ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


What Is a Portlet, Part 2

by Sunil Patil
02/01/2006
Portlets
"Portlets are web components--like servlets--specifically designed to be aggregated in the context of a composite page. Usually, many portlets are invoked to in the single request of a portal page. Each portlet produces a fragment of markup that is combined with the markup of other portlets, all within the portal page markup." (from the Portlet Specification, JSR 168)

In this Article:

  1. Using the Admin Console
  2. Adding Support for Edit Mode
  3. Using JSP to Display Help
  4. Portlet Request Processing
  5. Greet User with Personalized Message
  6. The Portlet Tag Library
  7. Portlet Preferences
  8. Caching
  9. Conclusion
  10. Resources

In "What is a Portlet," we started by talking about Portlet basics such as what portlets and portals are, how to create a simple portlet, and how to deploy it on the JSR 168 reference implementation, Pluto. This article talks about advanced Portlet topics by building on discussion in that article.

We will start our discussion with how to enable other portlet modes, such as EDIT and HELP, in the HelloWorld portlet. Then we will talk about how to use JSPs to generate markup. Next, we will discuss how portlet request processing works, in addition to how portlets make action processing easy for us. One of the new things in the Portlet specification is the concept of PortletPreferences, which allows the user to customize the view or behavior by storing configuration data as name-value pairs.

But let's begin by talking about the new admin console, which provides a UI for performing common administrative tasks.

Using the Admin Console

The good news on the Pluto side is now it has an admin console, which takes much of the pain out of Pluto administration. To use the console, download any version of Pluto after RC-3 and install it on your machine. Newer versions of Pluto are based on Tomcat 5.5, which requires J2SE 5.0.

If you don't want to install J2SE 5.0, you can try a hack that worked for me. Begin by shutting down your Pluto server. Extract the new Pluto version (rc4, in this example) to a folder such as c:\temp, and then go to its webapps folder (i.e., c:\temp\pluto-1.0.1-rc4\webapps). Copy the pluto folder from here and paste it into your pluto-1.0.1-rc1\webapps folder. It will ask if you want to overwrite; say "yes." Then start your rc1 server; this should show you a link to admin console in the panel on the left side.

Figure 1 shows what your browser will look like when you access an admin console.

Thumbnail, click for full-size image.
Figure 1. Admin console (click for full-size image)

In the first installment, we discussed pageregistry.xml and portletentityregistry.xml, and how Pluto looks at these files to find out how a particular page should be displayed. The basics remain same in the newer versions of Pluto--configuration information is still stored in these files. The difference is that you don't have to edit these files manually any more. Instead you can use admin console portlets to changes these files. Click on the Admin link in the left panel to open Pluto's admin console, which has the following three portlets:

  1. Deploy War Admin Portlet: This is the main (and only interactive) portlet in the admin console. It allows you to either install a new portlet or update existing portlet. It will modify configuration files such as portletentityregistry.xml, pageregistry.xml, and portletcontexts.txt.
  2. Portlet Entity Registry: This non-interactive portlet displays content in the portletentityregistry.xml file.
  3. Page Registry: This non-interactive portlet is a view of data contained in the pageregistry.xml file.

The admin console can be used either for installing new portlets or updating existing portlets. First, we will talk about how to use the admin console to install HelloWorld.war. Inside Deploy War Admin Portlet, click Browse, choose the HelloWorld.war file, and click Submit. This will deploy the HelloWorld portlet to Pluto. Specifically, this means Pluto will unzip the HelloWorld.war file in the webapps directory, and add entries to the portletentityregirstry.xml and porteltcontexts.txt files. You will see these changes reflected in the Portlet Entity Registry Admin portlet.

On the next screen, Pluto will ask you about layout information for the HelloWorld portlet. In the title field, you should specify name of the link (i.e., "Hello World") that will appear on the left-hand side, along with the number of rows and columns needed to display all portlets in the application. In the HelloWorld.war application, we have only one portlet, so we will keep default values of one row and one column. Click on Submit to make these changes and go to the Page Layout page, where you can choose which portlet is to be displayed in which row or column. Once you click submit this form, this will make an entry in the pageregistry.xml file. Our sample application has only one portlet, which will be selected by default, so all we have to do is click Submit. Now restart Pluto to get see the new link on the left-hand side.

If you want to re-deploy the HelloWorld portlet, go to the Deploy War Admin portlet, click Browse, and choose HelloWorld.war. When you click Submit, it will update the existing application, and on the next page it will show you a message saying you are trying to update an existing portlet. You should skip the subsequent pages, because when you're updating an existing portlet, no changes need to be made to portletentityregistry.xml and pagecontexts.txt (i.e., configuration files).

Head First Servlets and JSP

Related Reading

Head First Servlets and JSP
Passing the Sun Certified Web Component Developer Exam
By Bryan Basham, Kathy Sierra, Bert Bates

Adding Support for Edit Mode

As discussed in the first article in this series, portlets use a concept of portlet modes to indicate what the user is doing. The portlet specification defines three modes:

  1. VIEW: For activities related to the expected functionality of your portlet.
  2. EDIT: For configuration activities.
  3. HELP: For displaying help.

We will take the HelloWorld.zip sample portlet developed in the first installment and modify it to enable the EDIT mode.

  1. Change your HelloWorld.java file like this:

    public class HelloWorld extends GenericPortlet{
    protected void doView(RenderRequest request,
    RenderResponse response) 
    throws PortletException, IOException{
      response.setContentType("text/html"); 
      response.getWriter().println("Hello World Portlet");
     }
    protected void doEdit(RenderRequest request, 
    RenderResponse response) 
    throws PortletException, IOException {
      response.setContentType("text/html");
      response.getWriter().println(
        "Hello World Portlet- Edit Mode");
     }
    }

    In order to enable EDIT mode, we have to override the doEdit() method in HelloWorld.java. The doEdit() method gets called when the portlet is opened in EDIT mode, and supposed to generate markup. Inside of the doEdit() method, we first want to declare that the content type is text/html, and then write a one-line message informing user that he or she is accessing the HelloWorld Portlet in EDIT mode.

  2. Change the portlet.xml file like this.

    <supports>
      <mime-type>text/html</mime-type>
      <portlet-mode>VIEW</portlet-mode>
      <portlet-mode>EDIT</portlet-mode>
    </supports>

    This change in portlet.xml will inform the portlet container that the HelloWorld portlet supports EDIT mode in addition to VIEW mode. Adding the <portlet-mode>EDIT<portlet-mode> subelement to the <supports> element is what accomplishes this. The <supports> element tells the container two things: the HelloWorld portlet can generate text/html-type content, and that for text/html content, it supports VIEW and EDIT modes.

In addition to the pre-defined VIEW, EDIT, and HELP modes, the portlet specification allows portlets to take advantage of vendor-specific portlet modes. For example, IBM's WebSphere portal server has the concept of a CONFIG mode, which allows an administrator to configure the portlet. To use this, you can declare a custom CONFIG mode in your portlet.xml and add the corresponding doConfig() method in your HelloWorld.java method (actually it requires a few more steps, described in the portlet specification). As a result, when the HelloWorld portlet is deployed on IBM's WebSphere Portal Server, it will display an icon for CONFIG mode in addition to the other modes. But if this portlet is deployed on some other portlet container, the user won't be able to enter CONFIG mode.

Using JSP to Display Help

One of the problems with the HelloWorld sample portlet is that we are generating markup inside of Java code, specifically in the HelloWorld.java class. This approach is not acceptable for developing an industrial-strength portlet. In that case, you may want to keep your business logic inside of the Java code and pass control to a JSP page for markup generation. Portlet Specification 1.0 says that portlet container should be J2EE-1.3-compliant. A portlet can pass control to either a servlet or a JSP for markup generation. Portlets use a concept of a RequestDispatcher, similar to the class of the same name in the servlet API. Lots of work is going on to port popular servlet-based MVC frameworks to the portlet environment, such as the Struts, JSF, and Spring frameworks.

Now we will change our HelloWorld portlet to do two things: enable HELP mode, and use the Help.jsp page for generating markup. Follow these steps.

  1. Create \html\Hello.jsp under the Web Content folder. Add only one line in this file, saying "Help.jsp - HelloWorld Portlet in Help mode."

  2. Change HelloWorld.java to add the doHelp() method, like this:

    protected void doHelp(RenderRequest request, 
    RenderResponse response) 
    throws PortletException, IOException {
     PortletRequestDispatcher dispatcher = 
        getPortletContext().getRequestDispatcher
            ("/html/helloworld.jsp");
     dispatcher.include(request, response);
    }

    In this method, you first get the handle to PortletRequestDispatcher by passing it the path of JSP that you want to use for markup generation. Next, you pass control to that JSP by calling the include() method of the PortletRequestDispatcher.

Portlets generate markup fragments that are aggregated in a portal page document. Because of this, there are some rules and limitations in the markup elements generated by portlets. Portlets should conform to these rules and limitations when generating content.

  1. Portlets should not use tags that impact content generated by other portlets or that may even break the entire portal page. Inclusion of such a tag invalidates the whole markup fragment.
  2. Portlets generating HTML fragments must not use the following tags: base, body, iframe, frame, frameset, head, html, or title.
  3. Portlets generating XHTML and XHTML-Basic fragments must not use the following tags: base, body, iframe, head, html, or title.

Portlet Request Processing

Now it's time to talk about a most interesting aspect of portlet technology: how portlets process a request. We will take one backward step first. In the previous article's How a Portal Page is Created section, we said that most portals are implemented as web applications, and are deployed in an application server. In the case of Pluto, the org.apache.pluto.portalImpl.Servlet is mapped to receive all requests. So the first point of contact is a servlet that gets the browser request and generates markup.

Play little with your HelloWorld application a little bit, and you will note that whenever you are interacting with one of the portlets, the whole page gets refreshed. In other words, if you perform some action on Deploy War Admin Portlet, the complete page will get refreshed instead of only that portlet.

Let's look at an example of what happens when you're interacting with a portal. We will assume that you have two portlets, called A and B, on one page. Portlet A can get a request in either of two cases.

  1. The user clicked a link or submitted a form in Portlet A. In this case, Portlet A should do something, like execute some business logic and generate new markup. This is called action processing because the user is performing some action on Portlet A.

  2. The user is performing some action on Portlet B, and as a result, the complete page needs to be regenerated. Please note that when the user performs this action, the browser won't only update the content of Portlet B; instead, it will ask the portal server to regenerate the complete page. In this case, Portlet A should not execute any business logic; instead, it should regenerate markup. This is called the render phase.

Figure 2 shows how a request is handled in the portal server.

Portal Request Handling
Figure 2. Portal request handling

When the user clicks a link on Portlet A, the browser will submit the request to the portlet container. The portlet container will be notified that some action was performed on Portlet A, and will call the processAction() method of Portlet A. This is called the action processing phase. Once action processing is complete, the container will start the render phase. In this phase, the container figures out how many portlets are on the requested page. In this case, there are two portlets, A and B, so it will call the render() method of both these portlets in different threads. Once control returns back from both these methods, it will take the content/fragment generated by them and aggregate it to build the complete page. So when the user performs action on one of the portlets, the page gets the processAction method call and all portlets on the page get a render method call.

What happens if user clicks the Refresh button? Or what if the user visits or returns to the page where both of these portlets are placed? The container has to generate markup for the page, but no action is performed on any of the portlets. In this case, when the container gets the request, it will skip the action phase and in the render phase it will call the render() method of all portlets on the page.

The portlet interface has two different methods to represent two different phases of request processing.

  1. processAction(ActionRequest actionRequest, ActionResponse actionResponse): The portlet container will call this method when some action is performed on a portlet. It will pass information submitted by the user, such as values entered in a form, in an ActionRequest object. You can use the ActionResponse to redirect the user to a new page.

  2. render(RenderRequest renderRequest, RenderResponse renderResponse): This method is similar to the service() method in the Servlet API. It is called in the render phase, but you should not override it directly. Instead, you override one of the mode-specific methods, such as doView(), doHelp(), or doEdit(). The render() method will determine the mode requested by the user and call the appropriate doMode() method. The portlet container will pass a RenderRequest and RenderResponse to this method. The developer can write markup via the object returned by renderResponse.getWriter(). But RenderRequest does not directly get HTML form values submitted by the user--instead, it has go through PortletSession. This is getting complicated, so we will handle it separately in the next section.

Greet User with Personalized Message

In last section we talked about how the portlet container processes requests. In this section, we will clarify portlet request processing concepts by changing the HelloWorld portlet to handle the processAction() method call. What we will do is ask user for his or her name in EDIT mode, and then use that name to greet the user in VIEW mode. Follow these steps:

  1. Change the HelloWorld.java file, like this.

    protected void doView(RenderRequest renderRequest, 
    RenderResponse renderResponse)
    throws PortletException, IOException {
    renderResponse.setContentType("text/html");
      String userName = (String) renderRequest.
        getPortletSession().getAttribute("username");
      if (userName != null)
        renderResponse.getWriter().println("Hello " 
          + userName);
      else
        renderResponse.getWriter().println("Hello how 
         are you doing. Go to edit mode and set name");
    }
    
    protected void doEdit(RenderRequest renderRequest, 
    RenderResponse renderResponse)
    throws PortletException, IOException {
      PortletRequestDispatcher dispatcher = 
      getPortletContext().getRequestDispatcher(
        "/html/username.jsp");
      dispatcher.include(renderRequest, renderResponse);
    }
    
    public void processAction(ActionRequest 
    actionRequest, ActionResponse actionResponse) 
    throws PortletException, IOException {
      String userName =
        actionRequest.getParameter ("username");
      actionRequest.getPortletSession().setAttribute
        ("username", userName);
      actionResponse.setPortletMode(PortletMode.VIEW);
    }

    As you can see, we have to make lots of changes in HelloWorld.java in order to greet the user with his or her name. Let's take a look at each of these changes.

  2. Create /html/username.jsp inside of the Web Content folder. This JSP will be used to solicit input from the user in EDIT mode.

    <%@ page language="java"%>
    <%@ taglib uri="http://java.sun.com/portlet" 
    prefix="portlet"%>
    <portlet:defineObjects/>
    <form action='<portlet:actionURL />'>
    <table>
    <tr>
            <td>User Name</td>
            <td>
                    <input type="text" name="username"/>
            </td>
    </tr>
    <tr>
            <td>
                    <input type="submit" label="Save"/>
            </td>
    </tr>
    </table>
    </form>

    This JSP is like any typical JSP with the difference of the <portlet:defineObjects> tag. The portlet specification defines the portlet tag library with the aim of solving common problems faced by portlet developers. The portlet tag library is very important concept, and we'll revisit it shortly.

Build your application and deploy it on Pluto. When you go to VIEW mode, it will ask you to go to EDIT mode and enter a user name. Enter userName and click Submit; this should take you back to VIEW mode, which will display a message saying "Hello your-name." You know, one of the problems of being a UI developer is that you don't feel like you're learning something unless you see some change in the UI.

The Portlet Tag Library

The portlet specification mandates that every portlet container should provide an implementation of the portlet tag library, portlet.tld. The portlet tag library enables JSPs that are included by portlets to have direct access to portlet-specific elements such as RenderRequest and RenderResponse. It also provides JSPs with access to portlet functionality such as the creation of portlet URLs. JSP pages using the tag library must declare this in a taglib declarative statement like this (using the suggested prefix value):

<%@ taglib uri="http://java.sun.com/portlet" prefix="portlet" %>

Available tags include the following:

  1. <portlet:defineObjects/>: This tag must appear to define the following variables in the JSP page: renderRequest, renderResponse, and portletConfig. A JSP using the defineObjects tag may use these variables from scriptlets throughout the page.

  2. <portlet:actionURL/>: The portlet actionURL tag creates a URL that must point to the current portlet and must trigger an action request with the supplied parameter. The parameters may be added to the URL by including the param tag between the actionURL start and end tags:

    <portlet:actionURL windowState="maximized" portletMode="edit">
            <portlet:param name="action" value="editStocks">
    </portlet:actionURL>

    The example creates a URL that brings the portlet into EDIT mode and MAXIMIZED windowState to edit the stock quote list.

  3. <portlet:renderURL/>: This tag creates a URL that must point to the current portlet and must trigger a render request with the supplied parameter. The renderURL tag also takes parameters using the param tag, and allows the attributes forwindowState and portletMode.

  4. <portlet:namespace/>: This tag produces a unique value for the current portlet. Namespacing ensures that the given name is uniquely associated with this portlet and avoids name conflicts with other elements on the portal page or with other portlets on the page.

    <A HREF="javascript:<portlet:namespace/>test()">Encoded name</a>

Portlet Preferences

One of the requirement for portlets is that the user should be able to customize their view or the behavior of the portlet. The portlet specification provides you with a PortletPreferences object that allows you to store configuration data as name-value pairs. The most valuable part is that configuration is persisted, so it will be available across server restarts. You don't have to worry about how the portlet container is storing or retrieving configuration data. Another important point in the specification is that preferences attributes are user-specific. This means that you cannot design a portlet where changes made by an administrator are stored in PortletPreferences object and made available to all users (actually, some portlet vendors provide way to achieve this in a container-specific way).

We will make one more change to our HelloWorld portlet so that it remembers the userName even after a restart. Change HelloWorld.java like this:

protected void doView(RenderRequest request, 
  RenderResponse response)
  throws PortletException, IOException {
    response.setContentType("text/html");
    String userName =
      (String)request.getPreferences()
      .getValue("userName",null);
    if (userName != null)
          response.getWriter().println("Hello " + userName);
    else
      response.getWriter().println
        ("Go to edit mode and set name");
}

public void processAction(
  ActionRequest actionRequest, 
  ActionResponse actionResponse) 
  throws PortletException, IOException {
    String userName =
      actionRequest.getParameter ("userName");
    PortletPreferences pref =
      actionRequest.getPreferences();
    pref.setValue("userName",userName);
    pref.store();
    actionResponse.setPortletMode(PortletMode.VIEW);
}

We are not using the PortletSession object to store the userName attribute anymore. Inside processAction(), we first get the value of the userName attribute submitted by user, and set it in the PortletPreferences object by calling the method pref.setValue("userName",userName). The next step is to call the store() method on PortletPreferences, which saves the changes made in PortletPreferences to the persistence store.

There are a couple of important points to remember. First, you can modify PortletPreferences only inside of the processAction() method. Trying to call it during render() will result in an IllegalStateException. Secondly, if you don't call the store() method after changing PortletPreferences and before exiting the processAction(), then those changes will be discarded. The PortletPreferences interface also defines a reset() method, which can be used to reset values of preferences attributes to default values.

Caching

The portlet specification defines an expiration-based caching mechanism. This caching mechanism is per-portlet and per-client, with a restriction that cached content must not be shared across different user clients displaying the same portlet.

The way it works is that a portlet that wants its content to be cached using the content cache must define the duration of the expiration cache in a deployment descriptor, like this:

<portlet>
  <expiration-cache>300</expiration-cache>
</portlet>

Now suppose Portlet A has defined that it wants it content to be cached for 300 seconds and it is placed on a page along with Portlet B. If the user performs some action on Portlet B, the portlet container can choose to use cached content generated by Portlet A instead of calling its render() method again. But if the user performs some action on Portlet A, the container should invalidate the cached content of Portlet A and call its render() method once again.

Please note that it is not necessary for a portlet container to implement caching, and it can choose to disable caching any time it wants. But if the container does implement this feature, it helps improve portlet response time and reduce load on the server.

Conclusion

Portals strive to be the next-generation desktops for users. They will provide you with one desktop with different windows for all of the applications that you might need. It will be very easy for an administrator to apply policy on what you can or can not see. The administrator can also install or update new applications on the server and make those applications available to all users.

With JSR-168, one major hurdle of portlet technology advancement has been removed. Now if your portlet application is JSR-168-compliant, it can be deployed on any compliant server and it will appear as one window on the user's desktop.

Resources

Sunil Patil has worked on J2EE technologies for more than five years. His areas of interest include object relational mapping tools, UI frameworks, and portals.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.