Guidelines for Writing JSR-168 Portlets

by Drew Varner
01/24/2007

Abstract

JSR-168 is a collection of Java APIs for portlet developers. There are a number of reasons to design JSR-168 portlets that adhere to the specification. Portability is an obvious benefit. Code written according to the specification will be easier to move to among portal servers. The majority of Java-based portal servers support JSR-168 portlets.

Another benefit is easier federation. Exposing JSR-168 Portlets via Web Services for Remote Portlets (WSRP) producers is easier when portlets adhere to the JSR-168 specification. WSRP provides a standard to federate portlet content via Web services. JSR-168 and WSRP 1.0 portlet capabilities are tightly coupled. JSR-168 to WSRP portlet bridges utilize JSR-168's URL rewriting APIs. This article illustrates best practices for developing JSR-168 portlets for portability.

1. Always Utilize the URL Rewriting APIs for Content in Your Portlet

Java developers often write the URL to an image from a JSP like this:

<img src="/<%= request.getContextPath()%>/images/logo.gif"/>

This is incorrect in a JSR-168 portlet. The correct method is:

<img src="<%= renderResponse.encodeURL(renderRequest.getContextPath()+ 

    "../../images/logo.gif") %>"/>

The encodeURL() method can take either a full path URI or a fully qualified URL. Full path URIs are the most common. Use this technique when the resource is embedded in the Web Application Archive (WAR) with your JSR-168 portlet. Fully qualified URLs are used when the image is sitting on a separate server. An example would be a caching server dedicated to serving static content, offloading traffic from the portal server. While you can use encodeURL() with fully-qualified URLs to reference content outside your portlet, you should do so only if the resource is not accessible by the client. If the client can browse to the resource directly, do not use encodeURL() on the URL. For example, if you have a Web server for static content within your firewall that portal users cannot browse to directly, call encodeURL(). If it is outside your firewall and the portal users can browse directly to the web server, do not call encodeURL().

2. Do Not Append Paths to a Rewritten URL

The URL passed into RenderRequest's encodeUrl() must be complete before the method call. Portions of the URL cannot be appended after the method call. For example, if you want to generate a URL from an XSLT transformation, you cannot pass the encoded base URL ( http://foo.com/) as a parameter and append paths ( pages/bar.jsp) to the encoded base in the transform.

The following call demonstrates the correct way to encode a URL to an image:

<@= renderResponse.encodeURL(renderRequest.getContextPath()+

     "../../images/logo.gif")@>

It generates the following HTML fragment in BEA WebLogic Portal 9.2 using a .portal file:

<img src="http://localhost:7001/PortalWebApp/images/logo.gif;PORTAL_TAU=W3f6FbmLLcgZq9Fpv1JHLs5rrJG8Lgj2nnDVJqdfShhRGFnsqCKZ!-545815275"/>

The following call is incorrect. The URL will not point to the intended resource.

<@= renderResponse.encodeURL(renderRequest.getContextPath()+

     "../../images/")+"logo.gif"@>

It generates the following HTML fragment in WebLogic Portal 9.2 using a .portal file:

<img src="http://localhost:7001/PortalWebApp/images/;PORTAL_TAU=W3f6FbmLLcgZq9Fpv1JHLs5rrJG8Lgj2nnDVJqdfShhRGFnsqCKZ!-545815275logo.gif"/>

3. Qualify Client-side Script Variables and Methods with Namespaces

Let's say you want to validate user input using JavaScript in your portlet. The following JavaScript function could be useful:

<script>

function validate(foo) {

    if (foo.bar.value=="") {

        return false;

    }

    return true;

}

</script>

It is possible that another portlet in the same page will also have a JavaScript method named validate() with different logic. The portal framework itself may employ JavaScript methods. The solution to this problem is to namespace methods and top-level variables in your client-side scripts. The <portlet:namespace/> tag will generate one unique identifier per portlet. The first step is to include the portlet tag libraries in your JSP via a taglib directive.

<%@taglib uri="#" prefix="portlet"%>

The validate() method in the script can be differentiated with the tag.

<script>

function validate<portlet:namespace/>(foo) {

    if (foo.bar.value=="") {

        return false;

    }

    return true;

}

</script>

Here is how you call the namespaced JavaScript method:

<form action="http://www.somesite.org/servlet"

method="GET" onsubmit="return validate<portlet:namespace/>(this);">

<label for="bar">Text(required): </label>

<input type="text" name="bar" id="bar">

</form>

4. Ensure Inline Client-side Scripts that Refer to Portlet Resources Follow the Spec

Client-side scripts often reference external resources (images, movies, external pages) to enhance the user interface. A common example is JavaScript that preloads images to make swapping images efficient. Here is an example:

<script>

function preloadImages(){

    var menuImage =

    new Image();

    menuImage.src = "images/icon.gif";

    var menuImageDark=new Image();

    menuImageDark.src = "images/icon.gif";

}

</script>

URLs in these client-side scripts must be rewritten according to the JSR-168 specification. These scripts have to be in a JSP or JSR-168 portlet class to call the URL rewriting APIs. They cannot be in a standalone JavaScript (.js) file. Here is what a properly namespaced script with URL rewriting looks like in a JSR-168 portlet:

<script>

function <portlet:namespace/>preloadImages(){

    var menuImage = new Image();

    menuImage.src = "<%=renderResponse.encodeURL(renderRequest.getContextPath()+ "images/icon.gif")%>";

    var menuImageDark= new Image();

    menuImageDark.src = "<%=renderResponse.encodeURL(renderRequest.getContextPath()+ "images/icon_dark.gif") %>";

}

</script>

5. Always Declare a Content Type for Portlet Responses

According to the JSR-168 specification, "A portlet must set the content type of the response using the setContentType method of the RenderResponse interface." A portlet that does not explicitly set its content type will compile successfully. WebLogic Portal will not execute a portlet that does not set its content type. Ensure your portlets set their content type.

This example demonstrates a portlet correctly setting its content type:

public class MyPortlet extends GenericPortlet {

    public void doView(RenderRequest request, RenderResponse response)

        throws PortletException, IOException {

        response.setContentType("text/html");

        PrintWriter writer = response.getWriter();

        writer.println("<p>I set my content type!</p>");

    }

}

This example is incorrect but will still compile:

public class MyPortlet extends GenericPortlet {

    public void doView(RenderRequest request, RenderResponse response)

        throws PortletException, IOException {

        // no content type set!

        PrintWriter writer = response.getWriter();

        writer.println("<p>I did NOT set my content type!</p>");

    }

}

6. Do Not Send Cookies from Portlets

According to the JSR-168 portlet specification, calling addCookie() on HttpServletResponse does not actually set a cookie. Portlet containers that allow you to set a cookie are broken. Do not call this method.

If you'd like to persist information on a per-user basis while they are using the portal, you can persist the information as an attribute in the portlet's session. If you'd like to persist information after the user logs out, you can persist it in a data store (file system, database, LDAP, for example).

7. Separate Business Logic from Presentation

Experienced developers know that model viewer controller frameworks like Struts or Beehive make development of rich Web applications easier. The same holds true for portlets. JSR-168 is not the only deal in town for platform-independent portlets. WSRP portlets are portable among portals that implement the standard including non-Java portals. WebLogic Portal can expose Beehive and Struts portlets via WSRP.

If you are required to deploy portlets as JSR-168 WARs you still have options. The simplest technique to separate business logic from presentation logic in a JSR-168 portlet is to dispatch to a JavaServer Page (JSP). The portlet handles business logic in the rendering methods( render(), doView(), for example). The portlet passes information to a JSP using either an application-wide scope or the portlet scope. This example dispatches a request for a portlet to a JSP, passing a String in the portlet's scope:

public void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException {

        response.setContentType("text/html");

        request.setAttribute("foo","bar");

        String jsp = "/pages/portal.jsp";               

        PortletContext ctx = getPortletContext();

        PortletRequestDispatcher dispatcher = ctx.getRequestDispatcher(jsp);            

        dispatcher.include(request, response);

}

The value of the path to the JSP ( jsp in the example above) does not include the context path of the portlet's Web archive (WAR).

JSR-168's dispatching methods allow separation of business logic and presentation. However, they lack the sophistication of mature MVC frameworks.

Frameworks for JSR-168 development include:

Struts Action 2 is a combination of Struts and WebWork, so the portlet codebases are nearly identical for now. These frameworks simplify the development and maintenance of complicated portlets.

Summary

Sticking to these guidelines will keep your portlets in line with the JSR-168 specification. Adhering to the specification makes it easier to move your portlets among Java portal servers. It also makes it easier to federate your portal's content using WSRP.

Resources

Drew Varner is a senior principal consultant in BEA's Federal Professional Services practice. He has spent the past two years working with defense and intelligence agencies on implementations, including JSR-168 and WSRP portal technologies.