Channels ▼
RSS

JVM Languages

Developing JSR-168 Portlets

Source Code Accompanies This Article. Download It Now.


October, 2005: Developing JSR-168 Portlets

Ted and Martin are senior technologists at Wingspan Technology. They can be reached at info@wingspantech.com.


Portlets are applications viewed inside a portal framework. With the increased adoption of Java portals, both as intranets and public sites, comes the need to separate portlet functionality from the portal to both maximize code reuse and limit your dependency on specific vendors.

It used to be that if you wanted to migrate portlet functionality from one portal to another, you would have to rewrite massive amounts of code. If you were lucky and planned for this potential scenario in the beginning, you might have been able to limit your changes to the JSP and minimize the changes to the supporting classes.

Now, as portal vendors begin to support the Java Specification Request #168 (http://www.jcp.org/en/jsr/detail?id=168), this is becoming less of an issue for portlet developers. Writing JSR-168 portlets lets you become portal agnostic and lets vendors support a wider spectrum of platforms with little to no code changes.

In this article, we examine a portlet that Wingspan Technology (where we work) developed and contributed to the open-source community. This "Published Folder" portlet is used to expose the contents of a particular folder within EMC's Documentum eContent Server content repository (http://www.documentum.com/). We then get into the specifics of how and why this was done.

A portal is a web application whose primary purpose is to aggregate web content in portlets. While a portal may also provide user management and authentication, personalization, and other functionality across portlets, the role we focus on is the portlet container. Some of the more popular commercial Java portals are Vignette Application Portal, IBM WebSphere Portal, BEA WebLogic Portal, Oracle Portal, and Plumtree Corporate Portal. With the introduction of the JSR-168 specification, open-source portals are starting to take large bites out of the portal market. The leading open-source JSR-168 portals currently are eXo, Liferay, JetSpeed, and uPortal. One of the most common public examples of a portal site is my.yahoo.com. Table 1 presents the results of an informal poll of portal popularity that was conducted by Punit Pandey (http:// portlets.blogspot.com/).

Portlets are the meat within the portal. They provide the real content that the end user is looking for. A portlet could be as simple as a design element composed of images or text, or it could have more personalized content such as a local weather forecast. There are no limits to the complexity of a portlet. For example, a single portlet could act as the entire web interface for some legacy system. In some portals, like eXo, the portlets are even responsible for the portal navigation.

The JSR-168 portlet specification defines a set of APIs to address portlet aggregation, personalization, presentation, and security. Its main goals were that the portlets be simple, client agnostic, and secure. They would also support localization and internationalization, hot deployment, and remote execution.

Enterprise Content Management (ECM), sometimes referred to as "Enterprise Document Management," is a type of application that administers the storage, organization, classification, and retrieval of company data. This is typically in the form of a document repository. Additional functionality such as workflow, search, versioning, and access control are also standard. Some examples of ECM solutions are Documentum, FileNet, and SharePoint.

The Requirements

The requirements of this project can be broken down into two areas—characteristics and functionality. People working on projects with similar required characteristics would be wise to consider developing JSR-168 portlets as all or part of their solution. For our purposes, the requirements were:

  • The ability to run on a variety of portal applications.
  • Simple to install and configure.
  • Limit additional libraries that must be shipped with the application.

The key functional requirements were to:

  • Allow any number of users to access content in Documentum without specifying Documentum credentials.
  • Provide Administrators with a mechanism to specify the folder to display and connection information for Documentum.

The Challenges

Most of the challenges we encountered were a result of JSR-168 being in its infancy. Because of this, the different portals have varying levels of support. For example, some of the portals support custom window modes and states while others only support a handful of their own predefined custom states. The installation procedures can also vary greatly between the portal applications. Few of the portals support direct import of the JSR-168 portlet Web Archive (WAR) file. Many of them require the WAR to be run through a preparation tool or some other multistep deployment process.

In addition to lack of support, there are also serious bugs with some of the portal and application servers. One common problem is with versions of Apache Tomcat prior to 5.5. Any portals using the older versions of Tomcat, including Pluto (the reference implementation), have problems using the application session across web contexts. This means that the portlets cannot use the session as servlets in the same WAR distribution. This is an important consideration if the client accesses servlets outside of portlet and you need to share data between the two (http://issues .apache.org/jira/browse/PLUTO-53/).

Another challenge is the differences in portal architectures. Some portals require user authentication or at least have a built-in mechanism for logging in users. Others, however, make that optional or leave it as a function that a custom portlet must implement. This presents a problem when you want to provide administrative functionality through a special view or page. Several portals, such as eXo, Vignette, and IBM, support a custom "config" portlet mode to accomplish this very thing. But because this is not a standard mode, we avoid using it. We ultimately decided to avoid the user authorization problem entirely by just storing our configuration settings in a properties file. The downside to this solution is that every instance of the portlet within the portal shares the configuration settings.

The Architecture

There are three main sections to the DocWay Published Folder portlet in terms of architecture:

  • JSR-168 Portlet Class.
  • Display JSP and Class.
  • Content-Retrieval Classes.

The JSR-168 portlet class is PublishedFolder.java (Listing One). This is the main entry point for the portlet and is the only class that the portal framework is really aware of for this project. The portlet class loads the Java Server Page (JSP) file that is, in turn, responsible for calling the content classes and displaying the folder contents. When users click on a link, the content-retrieval classes stream the content to the browser.

The Code

The PublishedFolder class implements the interface that the portal uses to communicate with the portlet, and controls the activation of the JSP file. In this case, we extend the javax.portlet.GenericPortlet abstract class. We could have alternatively implemented the javax.portlet.Portlet and javax.portlet.PortletConfig interfaces. However, because this portlet is so simple, it was much easier to extend the generic version and override the doView method. Typically, the portlet class is more complex as most portlets have multiple views (view, edit, help, and so on) and also have to handle user actions. The key line in this class is when the PortletRequestDispatcher is created by a call to getRequestDispatcher on the PortletContext. This is important because this is how our view.jsp file gets loaded. For details on deployment descriptors and other information, see "Introduction to JSR 168: The Java Portlet Specification (http://developers.sun.com/ prodtech/portalserver/reference/techart/ jsr168/pb_whitepaper.pdf).

The view.jsp file (Listing Two) that gets loaded by the portlet class retrieves the document listing from the content source and renders the results using the HTMLRenderer class. There are a couple of points to make about this JSP. First, there is no business logic. The JSP just gets and displays results. It also is the last line in terms of error handling before the exception or error propagates up to the portal level. This is why we catch Throwable instead of Exception. Because this portlet could be on a page with countless other portlets, we need to ensure that any problems we encounter do not bubble up and crash the entire page or possibly the entire portal server. This is not just a good idea out of consideration for the other portlets on the page, but it can greatly ease debugging efforts. The different portals handle page-level exceptions in varying ways. Some provide the actual exception details (message, source, and stack trace); however, some just report a portlet-level exception as an internal server error with an HTTP 500 message. It the latter case, not only are you prevented from determining the root cause of the error, but if the 500 error prevents access to administrative links you may not be able to even remove the portlet from the page without uninstalling it.

The abstract ContentSource class (Listing Three) is the start of the content-retrieval process. This simple class acts as the broker for the actual content-source implementations. By abstracting the repository-specific code, we are able to keep the display and portlet classes unchanged, regardless of the source of the documents. Classes that extend ContentSource reside in the com.docway.publishedfolder.content.impl package and must implement the getContentList and getContentDocument methods. The static ContentSource.getContentSource method currently just returns a new instance of DocumentumSource. However, this could be enhanced to use a class loader to instantiate the implementing class based on a property file setting. This way, there might be SharePointSource, JdbcSource, and the like, and the administrator can change the source without any recompiling. The DocumentumSource class is not shown, but all it does is make DQL queries return a list of documents or a single document for the abstract ContentSource methods.

Because the notion of a "document" is dependent on the data source, we encapsulate this in the ContentDocument class (Listing Four). The ContentDocument class assumes that all documents, regardless of the source, have at least some standard properties; namely, ID, name, content, and size. Other custom or nonstandard attributes are handled using an ArrayList of key/value pairs. We implemented this as an ArrayList instead of a Map because we wanted to be able to use the order in which the attributes are added as the order that they are rendered in the display. A Map would have required a separate mechanism for ordering the keys.

When the list is rendered in the HTML, the document's name will be a link to the document's content. The transmission of the document contents to the client machine is handled through a servlet, ContentServlet (Listing Five). The HTML link makes the call to the servlet passing the selected document's ID as a query string parameter. This is then handed off to the ContentSource to retrieve the document from the configured source. The servlet then copies the InputStream from the ContentDocument to the HttpServletResponse OutputStream so the data is sent directly to the client.

Content-source configuration, as mentioned previously, is achieved with a properties file. The published_folder.properties file (Listing Six) lists the user credentials to impersonate and the location of the folder to publish. The username, password, and domain properties make up the credentials and are self explanatory. Every client that loads the portlet is accessing the content source by impersonating the user that is represented by these credentials. The repository and location properties are used in conjunction to identify where the content is located. In the case of Documentum, the repository would be the name of the docbase and the location would be the r_object_id of the cabinet or folder to be published. If SharePoint were the source, these properties might be the site name and document library name, respectively. For a JDBC or filesystem source, they could identify the database and table or server and folder. Generally, the repository/location combination is enough to locate a "folder" in any content source. The document's ID could then be relative to one or both of these values depending on how identifiers are managed in the source. For example, in Documentum, the location property is not needed to retrieve the document contents because the repository (docbase) and document ID (r_object_id) are enough to uniquely identify the requested object.

Enhancements

There are several areas that are good candidates for future enhancements. Because the code was developed to easily extend the ContentSource class, and therefore, provide implementations for sources other than Documentum, this is the most obvious area for further development. Additional sources could be another ECM system such as SharePoint or FileNet, a JDBC data source, or even the file system. These could be done with as little as one class that extends the abstract ContentSource class.

Another enhancement would be to provide a caching mechanism at the portal level. This would limit the frequency of the queries against the content source. This could be a major performance gain if the query is time intensive because such queries currently are reexecuted with each page refresh.

A third possible enhancement deals with the configuration challenge mentioned previously. A way to set the credential and repository information per portlet instance instead of per portal would make the solution more flexible and usable.

This portlet has been contributed to the open-source community with a GNU General Public License (GPL). It is now hosted on SourceForge, so feel free to download the code to try it out or contribute your own enhancements. For more information on the DocWay Published Folder, see http://sourceforge.net/projects/docway/ and http://www.wingspantech.com/.

DDJ



Listing One

package com.docway.publishedfolder.portlet;
import java.io.IOException;
import javax.portlet.*;
 
/** Main portlet class. Handles render requests from portal framework. */
public class PublishedFolder extends GenericPortlet
{
    public void doView(RenderRequest request, RenderResponse response)
            throws PortletException, IOException
    {
        response.setContentType("text/html");
        final PortletContext context = getPortletContext();
        final PortletRequestDispatcher rd = context
                .getRequestDispatcher("/view.jsp");
        rd.include(request, response);
    }
}
Back to article


Listing Two
<%@ page import="com.docway.publishedfolder.content.ContentSource,
            com.docway.publishedfolder.content.ContentDocument, 
            com.docway.publishedfolder.HTMLRenderer" %>
<%
String html;
try
{
//retrieve document listing from content source
final ContentSource source = ContentSource.getContentSource();
    final ContentDocument[] documents = source.getContentList();
    //convert the results to HTML
    html = HTMLRenderer.render(documents);
}
catch(Throwable t)
{
//convert the exception or error to HTML
html = HTMLRenderer.render(t);
}
//display the HTML
out.println(html);
%>
Back to article


Listing Three
package com.docway.publishedfolder.content;
import com.docway.publishedfolder.content.impl.*;

/** Class used to retrieve ContentDocuments and abstract the source
 * specific functionality and packages. */
public abstract class ContentSource {
    /** Gets subclass that implements any content source specific functions.
     * @return ContentSource implementation */
    public static ContentSource getContentSource() {
        // For now this just returns Documentum implementation. This method
        // could be easily modified to determine implementation to use based
        // on the properties file or some other mechanism.
        return new DocumentumSource();
    }
    /** Retrieves an array of all documents that are located in location in
     * the repository that was set in the application's resource bundle. This
     * should only be used to display the list of documents and their
     * attributes. Since document contents are not streamed at this point the
     * ContentDocuments that are returned may not have that property populated
     * even though content might exist.
     * @return Array of ContentDocuments that are contained in the repository
     *         location or null if none found.
     * @throws Exception
     */
    public abstract ContentDocument[] getContentList() throws Exception;
    /** Retrieves an individual document by ID that is located in location in
     * the repository that was set in the application's resource bundle. This
     * should only be used to retrieve document contents. Since attributes are
     * not streamed the ContentDocument that is returned may not have its
     * attribute map populated even though additional attributes might exist.
     * @param contentID
     *            Unique ID of document to retrieve.
     * @return ContentDocument with requested ID or null if not found.
     * @throws Exception
     */
    public abstract ContentDocument getContentDocument(String contentID)
            throws Exception;
}
Back to article


Listing Four
package com.docway.publishedfolder.content;
import java.io.InputStream;
import java.util.*;

/** Class represents a document returned from a ContentSource. There are
 * several standard properties like id, name, content, and size. Any other
 * custom attributes can be stored in the attributes ArrayList.
 */
public class ContentDocument {
    private String id;
    private String name;
    private InputStream content;
    private long size;
    private ArrayList attributes = new ArrayList();
    /** @param id
     *  @param name */
    public ContentDocument(String id, String name) {
        this(id, name, null, -1);
    }
    /* @param id
     * @param name
     * @param content
     * @param size */
    public ContentDocument(String id, String name, InputStream content, 
        long size) {
        super();
        this.id = id;
        this.name = name;
        this.content = content;
        this.size = size;
    }
    public void addAttribute(String key, String value) {
        attributes.add( new Attribute(key, value));
    }
    public Attribute[] getAttributes() {
        return (Attribute[]) attributes
        .toArray(new Attribute[attributes.size()]);
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public long getSize() {
        return size;
    }
    public void setSize(long size) {
        this.size = size;
    }
    public InputStream getContent() {
        return content;
    }
    public void setContent(InputStream content) {
        this.content = content;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public static class Attribute
    {
        private String name;
        private String value;
        public Attribute(String name, String value) {
            this.name = name;
            this.value = value;
        }
        public String getName() {
            return name;
        }
        public String getValue() {
            return value;
        }
    }
}
Back to article


Listing Five
package com.docway.publishedfolder.content;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.http.*;

public class ContentServlet extends HttpServlet {
   private static final long serialVersionUID = 1L;
   private static final int CHUNK_SIZE = 2048;
   public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        try {
            final String contentID = request.getParameter("content_id");
            final ContentSource source = ContentSource.getContentSource();
            final ContentDocument document = source
                    .getContentDocument(contentID);
            response.setStatus(200);
            response.setHeader("Content-Length", Long.toString(document
                    .getSize()));
            response.setHeader("Content-Disposition","attachment; filename=\""
                    + document.getName() + "\"");
            copyStream(document.getContent(), response.getOutputStream());
        } catch (Exception e) {
            throw new ServletException(e);
        }
    }
    /** Copies an InputSteam to an OutputStream.
     * @param input
     * @param output
     * @throws IOException */
    private static void copyStream(final InputStream input,
            final OutputStream output) throws IOException {
        final byte[] chunk = new byte[CHUNK_SIZE];
        while (true) {
            final int i = input.read(chunk);
            if (i < 0) // eof
            {
                break;
            }
            output.write(chunk, 0, i);
        }
        output.flush();
    }
}
Back to article


Listing Six
### Sample Properties file for DocWay Published Folder portlet
### The domain property is optional.  All other properties are required
username=johndoe
password=jsr168rules
#domain=
repository=MyDocbase
location=0c00000180000112
Back to article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video