A well-behaved Jetspeed portlet

Design and construct a Jetspeed portlet that plays well with others

Jetspeed 2.0 is scheduled for release in early 2005. However, that hardly means that there is no more interest in Jetspeed 1.x. Jetspeed, the open source portal venture from the Jakarta Project, has been around for several years and is in use around the world.

This article presents a working example of how to construct a Jetspeed portlet that runs efficiently, adheres to the Model 2 architecture, and, by not interfering with additional portlets, plays well with others. In addition, I demonstrate some simple ways to improve performance and point out the mistakes that can cost you days of debugging time.

Before I go any further, let's get the prerequisites out of the way. This article's example is based upon Jetspeed 1.5 and has been tested with a development version of Jetspeed 1.6, which has not been finalized. Please note: This example is not compliant with Java Specification Request 168. JSR 168 is at the heart of Jetspeed 2.0 and is beyond this article's scope. However, a subsequent article will demonstrate how to recast this portlet to comply with Jetspeed 2.0.

This article's code is compiled with Java 1.4.2. I run Jetspeed on Tomcat 5.x; although the latest of version of Tomcat 4.x will probably function just as well. The HTML and JavaScript have been tested with both Internet Explorer and Firefox. In addition, I assume the reader is familiar with Jetspeed, has Maven installed, and has tried the simple Jetspeed tutorials.

Before we get started

Writing portlets is almost like writing servlets, and, in fact, can conform to the Model 2 architecture. However, portlets must function as part of a larger framework, both in terms of operations and the HTML page; therefore, it is critically important that early in your design, you consider the possibility that your portlet might collide with other portlets on the page. For example, if two different portlets both have a JavaScript function called "Submit_Form()", the page will generate a JavaScript error and neither portlet will function.

An intranet portal, such as Jetspeed, allows multiple instances of the same portlet on a page, with each assigned a unique portlet ID. Attributes of one instance can be changed without affecting the siblings, if everything is coded correctly. For example, suppose a portlet allows a user to change the background color. If that portlet appears three times on a portal page, a change in one of the instances' background colors should not affect the other instances—unless, by design, we want them to change. However, without exercising care in coding the view and the controllers, it is easy to confuse attributes and thus display incorrect data. Local data needs to be kept local.

This same portlet example exposes another potential problem on the generated HTML page: when the Change Color button is pressed on the first instance, how do we ensure that the JavaScript and the action controller for the second instance are not fired?

The opposite of local data is shared data. Imagine a portlet where all instances display the same data and appear identical—for example, a Telephone List portlet. Changing the help-desk phone number in one instance of the portlet should be reflected in all instances.

Decide early whether your portlet requires either a local or shared data model. This article's example portlet uses a local data model. (A follow-up article will demonstrate shared data and database access.)

Our portlet

For our example portlet, we want to create a better link-farm, or bookmark, portlet. A rudimentary bookmark portlet ships with Jetspeed. However, according to some discussions in the newsgroups, it might be removed from the distribution. A link farm contains a list of hypertext links (<A href=> tags) that jump the user to different Websites.

A bookmark portlet can be used in many ways: as a list of search engines, local restaurants, or, as developed for a law firm, a list of Websites associated with the California courts. Bringing these sites together in an easy-to-use format makes for an extremely powerful portlet. Experts in different fields in your organization can keep the farms fresh by updating the links, while normal users have read-only access. Though this type of portlet is frequently used on public pages, users can also add a bookmark portlet to their personal homepages as well.

Our business problem is fairly simple: Design a portlet that allows users to maintain lists of URLs that are associated with an optional description and image. In addition, only properly authorized users are allowed to add, edit, delete, and reposition the URLs. The portlet should also support two levels of security. Users with Level 1 security are allowed to make cosmetic changes, such as the number of columns of links to display or how much, if any, of the optional text to display. Users with Level 2 security have the right to edit the underlying data, the links themselves.

In addition, it must be possible to instantiate this portlet several times on the same portal page—this is not a specific business requirement, but rather a universal Jetspeed design consideration. This requirement may sound simple, but, as I discuss later, if a portlet's presentation is not properly designed, it does not play well with others, resulting in JavaScript and HTML errors.

Figure 1 shows a typical Jetspeed page with two instances of the LinkFarm portlet. One instance houses Search Engine links, while the second contains links to entertainment sites.

Figure 1. A sample page showing two instances of the LinkFarm portlet. Click on thumbnail to view full-sized image.

Our portlet adheres to the Model 2 architecture, an implementation of the Model-View-Controller architecture. MVC not only helps a developer design a portlet in clean, succinct pieces, it also helps a developer more easily distribute responsibilities and apply modifications later.

In designing a solution to our business problem, we have chosen the path that requires no third-party software or toolsets, and achieves our goals in the simplest way possible.

One of our business prerequisites requires the links to persist between user sessions. We have chosen to store the links in the portlet page's PSML (Portlet Structure Markup Language) file (if you are unfamiliar with PSML see Resources). Storing data on PSML pages has pros and cons:

  • Pros: It is simple. It requires no third-party software such as a database engine, and finally, it requires no knowledge of or reliance upon a directory structure.
  • Cons: While the portlet can be moved on the page, it can't be moved to another page. PSML entries describe the portlets on a particular page. Therefore, creating a stock portlet prepopulated with links inherited from the LinkFarm portlet and allowing people to add it to their own page would prove difficult. This problem has two possible solutions: Implement export/import functionality—that is, teach the portlet how to dump out, or read in, batches of links. Or move to database- or file-based persistence. However, in this article, we forego such functionality and stick with the simple, basic approach.

Parts of the puzzle

Our choice of the Model 2 architecture dictates that we break our design into three basic groups: the models, the views, and the controller. Before we can begin writing code, we must outline the components we need to build—one of the major commandments of good portlet construction: "Thou shall design before thou codes."

Model

For the models, we will need the classes listed below. (The full source code is available in Resources. However, I do not recommend jumping to the code just yet. I'll go over it in more detail later.) I've used the class names so you can easily locate the specific code. All models are in the com.automateddocsys.portal.modules.actions.portlets.linkfarm package.

  • LinkFarmLink: This contains the information for a single link. It has the URL, optional URL to an image, name (or short description), and long description. In addition, it has a position property—an integer, which is used to order the links for presentation purposes and identify them for editing. In this model, URL and image link are of the type URI.
  • DisplayLink: To work well with the edit view, we create a class where all of the properties are strings. We use the Builder pattern to create DisplayLink from LinkFarmLinks.
  • LinkFarmLinks: This class, which we inherited from ArrayList, has a clearly defined purpose: it maintains the list of links presented in any given instantiation of our LinkFarm portlet. In addition, it has some convenience methods. Swap() exchanges two links in the list. We use this method when the user moves the items on the presentation editing screen. Our particular implementation relies heavily on the absence of gaps in the link list's position IDs. The position IDs are required to identify a given link for editing and deleting. The Renumber() function ensures the links are consecutively numbered.

    As described earlier, we decided to persist the LinkFarmLinks to the PSML. PSML files are referred to as registries with registry entries; reading to and reading from PSML is accomplished by setting and getting attributes of the PortletConfigState, which I discuss in more detail when we actually examine the code.

  • LinkFarmPSMLPersister: This persistence class contains two methods, getLinks() and saveLinks(). In the real world, we would implement this class as an interface, thereby allowing the controller to change to database, file, or Web service persistence by simply changing the reference to one class.

View

Four views make up the lifecycle of our LinkFarm portlet. We start with the basic view (see Figure 2), which presents the links to the user. This view can be modified to vary the number of columns and to include the long description and/or the image. (Note: The actual implementations of the image URLs have been omitted from this example's code because they made it too complex.)

Figure 2. Jetspeed standard customization view. Click on thumbnail to view full-sized image.

Our second view, the AttributesCustomization view, is provided for free from the Jetspeed framework and allows users with proper security to edit parameter attributes not marked as "hidden." (For more on registry attributes, see Resources.) In our example, we expose columns, view, and editable—see the MyPortlets.xreg file in the source code:

 <parameter name="columns" value="4" type="int" hidden="false" 
       cachedOnName="true" cachedOnValue="true"/>
<parameter name="editable" value="true" type="boolean" hidden="false" 
       cachedOnName="true" cachedOnValue="true"/>
<parameter name="view" value="condensed" type="style" hidden="false" 
       cachedOnName="true" cachedOnValue="true">

Actually, our view attribute is a bit more complicated then shown here. We want to use a drop-down list-box on the Customize form. See the properties file for a complete example of how to set up the parameters for the drop-down function. A user with proper security can select the small pencil icon in the portlet's upper right-hand corner to bring up the AttributesConfiguration screen.

The DataConfiguration view is where the actual link data is displayed for editing, see Figure 3.

Figure 3. Our portlet in configuration mode. Notice the icons for up, down, edit, and delete. Click on thumbnail to view full-sized image.

In addition to merely displaying the data, this view also contains icons for moving the links up and down, editing, and deleting. Plus, another button adds new links.

Finally, we have the SingleLink view. Just as it sounds, this view presents and collects data on a single link, see Figure 4.

Figure 4. Adding and editing information for one link. Click on thumbnail to view full-sized image.

We could have combined several of these views in the same Velocity file and controlled the output with various #IF, #ELSE, and #END statements. But doing so is simply not good, clean design and would have violated another of the basic commandments: "Thou shall keep the simple things simple. There shall be no more complexity than required."

1 2 3 Page 1