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.
Before we get started
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.
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.)
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.
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.
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."
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
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
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
LinkFarmLinksto 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,
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.
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.)
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
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">
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.
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.
We could have combined several of these views in the same Velocity file and controlled the output with various
#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."