Understanding the Java Portlet Specification 2.0 (JSR 286): Part 1, Overview and Coordination Between Portlets

By Deepak Gothe, January 2010

In this three-part series, the articles describe new features available in the Java Portlet Specification 2.0 ( JSR 286) and extensions supported by OpenPortal Portlet Container 2.x.

Part 1 provides an overview of the Java Portlet Specifications and explains in detail one of the main features introduced in JSR 286: coordination between portlets. Part 2 describes the other new features introduced in JSR 286. Part 3 describes the extensions supported by OpenPortal Portlet Container 2.x.

Contents

Part 1 - Overview and Coordination Between Portlets

Overview

Portlets are web-based components that enable integration between applications and portals and thus enable delivery of applications on portals.

The Java Portlet Specification achieves interoperability among portlets and portals by defining the APIs for portlets. The Java Portlet Specification 1.0, Java Specification Request (JSR) 168, was released in October 2003. This brought a world of difference. By adhering to the standards, you can build portlets that can run in portals, irrespective of their vendors.

Since its release in 2003, JSR 168 has gone through many real-life tests in portal development and deployment. Although the community has identified "gaps," standards take time to evolve and become available to the public. Meanwhile, many portal vendors have been filling those gaps with their own custom solutions, which unfortunately results in portlets that are not portable.

In February 2006 the JSR 286 Expert Group was formed to start work on Java Portlet Specification 2.0, and the final version was approved in June 2008. This article describes the new features that are available in JSR 286, along with a sample application to demonstrate how to write portlets with these features.

The OpenPortal Portlet Container 2.x provides an implementation of the Java Portlet Specification 2.0. In addition to this, it also provides a portlet driver, which is a lightweight portlet rendering environment. This driver simulates some capabilities of a typical portal product, such as the Web Space Server, Liferay Portal.

Coordination Between Portlets

To provide coordination between portlets, the Java Portlet Specification, JSR 286, introduces the following mechanisms:

  • Events: Portlet events that a portlet can receive and send
  • Public Render Parameters: Render states that can be shared between portlets

Events

In JSR 168 (Portlet 1.0), the only way to achieve eventing between portlets was through a portlet session. This was possible between portlets that are in the same web application. JSR 286 (Portlet 2.0) defines a lifecycle for events, so that eventing is possible between portlets that are in different web applications.

An event is a lifecycle operation that occurs before the rendering phase. Events can be described as a loosely coupled, brokered means of communication between portlets. Events allow portlets to respond on actions or state changes not directly related to an interaction of the user with the portlet.

A portlet can declare events in its deployment descriptor by using the event-definition element in the portlet application section. In the portlet section, each portlet specifies the events it would like to publish through the supported-publishing-event element and the events it would like to process through the supported-processing-event element.

The supported-publishing-event and supported-processing-event elements must reference the event name defined in the portlet application section in an event-definition element.

The portlet creates events using the setEvent() method during action processing. The events are processed by the portlet container after the action processing has finished. Portlets can also create events during the event phase by calling the setEvent() method on EventResponse.

To receive events, the portlet must implement the javax.Portlet.EventPortlet interface. The portlet container calls the processEvent() method for each event targeted to the portlet with an EventRequest and EventResponse object. The portlet can access the event that triggered the current process event call by using the EventRequest.getEvent() method. This method returns an object of type Event encapsulating the current event name and value.

Event names are represented as QNames to identify them uniquely. The event name can be retrieved by using the getQName() method that returns the complete QName of the event, or by using the getName() method that returns only the local part of the event name. The value of the event must be based on the type defined in the deployment descriptor.

To create portlets that use the event feature:

Declare the events that will be published or processed in the portlet.xml file at the portlet application level. The event definition must specify a name and optionally an object type.

Note: The object must be serializable and must be annotated with the XML root element.

<portlet-app

  xmlns="http://bit.ly/14tO1Qi"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="
  http://bit.ly/14tO1Qi"
  id="myPortletApp" version="2.0">

    <portlet>
     . . . 
     . . .
    </portlet>
    <event-definition> 
        <qname xmlns:x="http:sun.com/mapevents">x:Continent</qname>
        <value-type>com.sun.portal.portlet.mapevent.Continent</value-type>
    </event-definition>
</portlet-app>

@XmlRootElement 
public class Continent implements Serializable {

    private String name;
    private String description; 

    public Continent() { 

    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    } 

}

If the portlet wants to publish an event, specify the event name as defined in the previous code example in the portlet section of the portlet.xml file.

<portlet-app ......>

    <portlet>
         <portlet-name>ContinentPortlet</portlet-name>
         <display-name>ContinentPortlet</display-name>
         <portlet-class>
              com.sun.portal.portlet.event.ContinentPortlet
         </portlet-class>
         . . . 
         <supported-publishing-event>
             <qname xmlns:x="http:sun.com/events">x:Continent</qname>
         </supported-publishing-event>
    </portlet>
    . . . 
</portlet-app>

If the portlet wants to process an event, specify the event name as defined in the previous code example in the portlet section of the portlet.xml file.

<portlet-app ......>

    <portlet>
        <portlet-name>ContinentMapPortlet</portlet-name>
        <display-name>ContinentMapPortlet</display-name>
        <portlet-class>
             com.sun.portal.portlet.event.ContinentMapPortlet
        </portlet-class>
        ........
        <supported-processing-event>
            <qname xmlns:x="http:sun.com/events">x:Continent</qname>
        </supported-processing-event>
    </portlet>

    <portlet>
        <portlet-name>ContinentInfoPortlet</portlet-name>
        <display-name>ContinentInfoPortlet</display-name>
        <portlet-class>
             com.sun.portal.portlet.event.ContinentInfoPortlet
        </portlet-class>
        ........
        <supported-processing-event>
            <qname xmlns:x="http:sun.com/events">x:Continent</qname>
        </supported-processing-event>
    </portlet>
    ........
</portlet-app>

Issue an event in the portlet that was specified as a supported-publishing-event event in the portlet.

public class ContinentPortlet extends GenericPortlet {


    public void processAction(ActionRequest request, ActionResponse response)
        throws PortletException,IOException {

        QName qname = new QName("http:sun.com/mapevents" , "Continent");
        String value = request.getParameter("continent");

        Continent continent = new Continent();
        continent.setName(value);
        ResourceBundle rb = getPortletConfig().getResourceBundle(
              request.getLocale());
        continent.setDescription(rb.getString(value));
        response.setEvent(qname, continent);
    }

    .........
}

Process the event in the portlet that has been specified as a supported-processing-event event in the portlet.

public class ContinentInfoPortlet extends GenericPortlet {


    public void processEvent(EventRequest request, EventResponse response) { 

        Event event = request.getEvent();
        if(event.getName().equals("Continent")) {
            Continent payload = (Continent)event.getValue();
            response.setRenderParameter(
                 "continentDescription", payload.getDescription()); 
        }
    }
    ........
} 

public class ContinentMapPortlet extends GenericPortlet { 

    public void processEvent(EventRequest request, EventResponse response) { 
        Event event = request.getEvent();
        if(event.getName().equals("Continent")) {
            Continent payload = (Continent)event.getValue();
            response.setRenderParameter(
                 "continentName", payload.getDescription());
        }
    }
   ........
}

Sample Application: Eventing Map

Figure 1 shows the World Map, Continent Map, and Continent Information portlets that participate in the event. Clicking on any continent in the World Map triggers an event. This event is processed by the Continent Information and Continent Map portlets to show the relevant information.

Eventing Map
Figure 1. Sample Application: Eventing Map

Using Wildcards

To use wildcards, the portlet must organize the local part of the event names in the event-definition element in a hierarchical manner using the dot (.) as a separator. For example: foo.event.one

Wildcards should be used only in the supported-processing-event or supported-publishing-event elements.

If you want the wildcard string to match a part of a hierarchy, two dots are required at the end of the wildcard string: one to denote the hierarchy and one for the wildcard. For example: foo..

The event name must be part of the hierarchy, not a substring. For example: foo. matches foo.bar, but it does not match football.

Consider the following example:

<portlet-app .......>

    <portlet>
        ... 
        <portlet-name>Portlet1</portlet-name>
        ... 
        <supported-publishing-event>
            <qname xmlns:x="http:example.com/events">x:foo.event.</qname>
        </supported-publishing-event>
        ...
    </portlet>

    <portlet>
        ...
        <portlet-name>Portlet2</portlet-name>
        ... 
        <supported-processing-event>
            <qname xmlns:x="http:example.com/events">x:foo.event.</qname>
        </supported-processing-event>
        ...
    </portlet>

    <portlet>
        ... 
        <portlet-name>Portlet3</portlet-name>
        ... 
        <supported-processing-event>
            <qname xmlns:x="http:example.com/events">x:foo..</qname> 
        </supported-processing-event>
        ...
    </portlet>

    <portlet> 
         ...
        <portlet-name>Portlet4</portlet-name>
        ...
        <supported-processing-event>
             <qname xmlns:x="http:example.com/events">x:foo.e.</qname>
        </supported-processing-event>
        ...
    </portlet>

    <event-definition>
        <qname xmlns:x="http:example.com/events">x:foo.event.one</qname>
        <value-type>com.example.Address</value-type>
    </event-definition>

    <event-definition>
        <qname xmlns:x="http:example.com/events">x:foo.event.two</qname>
        <value-type>com.example.Address</value-type>
    </event-definition>

    <event-definition>
        <qname xmlns:x="http:example.com/events">x:foo.bar.event</qname>
        <value-type>com.example.Address</value-type>
    </event-definition>

</portlet-app>
  • Portlet1 can publish the events x:foo.event.one and x:foo.event.two because the event definitions x:foo.event.one and x:foo.event.two both begin with x:foo.event.
  • Portlet2 can process the events x:foo.event.one and x:foo.event.two because the event definitions x:foo.event.one and x:foo.event.two both begin with x:foo.event.
  • Portlet3 can process the events x:foo.event.one, x:foo.event.two and x:foo.bar.event because the event definitions x:foo.event.one, x:foo.event.two, and x:foo.bar.event all begin with the hierarchy x:foo.
  • Portlet4 cannot process any event because there is no event that begins with the hierarchy x:foo.e. ( Note: e. and event. are two different hierarchies, because e. followed by the dot does not denote every hierarchy that starts with e not followed by a dot.)

Using Aliases

Aliases can be used to coordinate between two portlet applications that use different event names. Consider a portlet, PortletA, that uses lastname as an event name and a portlet, PortletB, that uses surname as an event name. The event published by PortletA cannot be processed by PortletB because the event names are different. By adding alias in the portlet.xml file of either PortletA or PortletB, PortletB can process the event.

To add aliases for portlet events:

Add an alias (the same as qname of the event of PortletB) in the portlet.xml file of PortletA as shown in the following code example.

Note: If you want to add an alias in PortletB, use the qname of the event of PortletA.

PortletA:




<portlet-app ......>
    <portlet>
        <portlet-name>PortletA</portlet-name>
        <display-name>PortletA</display-name>
        <portlet-class>com.sun.portal.portlet.PortletA</portlet-class>
         ........
        <supported-publishing-event>
            <qname xmlns:x="http:sun.com/events">x:lastname</qname>
        </supported-publishing-event>
    </portlet>
    <event-definition>
        <qname xmlns:x="http:sun.com/events">x:lastname</qname>
         
                  <alias xmlns:x="http://sun.com/events">x:surname</alias>
        <value-type>java.lang.String</value-type>
    </event-definition>
</portlet-app>

PortletB:



<portlet-app ...>
    <portlet>
        <portlet-name>PortletB</portlet-name>
        <display-name>PortletB</display-name>
        <portlet-class>com.sun.portal.portlet.PortletB</portlet-class>
        ........
        <supported-processing-event>
             <qname xmlns:x="http:sun.com/events">x:surname</qname>
        </supported-processing-event>
    </portlet>
    <event-definition>
        <qname xmlns:x="http:sun.com/events">x:surname</qname>
        <value-type>java.lang.String</value-type>
    </event-definition>
</portlet-app>
                      

When the event lastname is published in PortletA, it can be processed as surname in PortletB.

public class PortletA extends GenericPortlet {


    public void processAction(ActionRequest request, ActionResponse response)
        throws IOException, PortletException {
        ........
        QName qname = new QName("http://sun.com/events","lastname","x");
        response.setEvent(qname, "Doe");
        ........
    }
}

public class PortletB extends GenericPortlet {

    @ProcessEvent(qname = "{http://sun.com/events}surname")
    public void processEvent(EventRequest request, EventResponse response) {
        Event event = request.getEvent();
        String name = (String)event.getValue(); //name will be "Doe"
        ........
    }
}

Public Render Parameters

In JSR 168 (Portlet 1.0), the render parameters set in the processAction() method are available only in the render phase of the same portlet.

By using the public render parameters feature, the render parameters set in the processAction() method of one portlet are available in render parameters of the other portlets. Using public render parameters instead of events avoids the additional process event call. The public render parameters can also be set on the render URL.

To enable coordination of render parameters with other portlets within the same portlet application or across portlet applications, the portlet can declare public render parameters in its deployment descriptor using the public-render-parameter element in the portlet application section. Public render parameters can be viewed and changed by other portlets or components.

In the portlet section, each portlet can specify the public render parameters to be shared through the supported-public-render-parameter element. The supported-public-render-parameter element must reference the identifier of a public render parameter defined in the portlet application section in a public-render-parameter element.

To create portlets that use the public render parameters:

Declare the render parameters to be shared in the portlet.xml file by setting the public render parameters at the portlet application level.

<portlet-app ...>

    <portlet>
    ... 
    </portlet>
    <public-render-parameter>
        <identifier>zip-id</identifier>
        <qname xmlns:x="http://sun.com/params">x:zip</qname>
    </public-render-parameter>
</portlet-app>

Specify the render parameter that the portlet would like to share in the portlet section of the portlet.xml file.

<portlet-app ......>

    <portlet>
        <portlet-name>WeatherPortlet</portlet-name>
        <display-name>WeatherPortlet</display-name>
        <portlet-class>com.sun.portal.portlet.WeatherPortlet</portlet-class>
         ........
        <supported-public-render-parameter>
             zip-id
        </supported-public-render-parameter>
    </portlet>

    <portlet>
        <portlet-name>MapPortlet</portlet-name>
        <display-name>MapPortlet</display-name>
        <portlet-class>com.sun.portal.portlet.MapPortlet</portlet-class>
        ........
        <supported-public-render-parameter>
             zip-id
        </supported-public-render-parameter>
    </portlet>
    ........
</portlet-app>

Set the render parameter in the processAction() method by using the defined public render parameter identifier as the key.

public class WeatherPortlet extends GenericPortlet {


    public void processAction(ActionRequest request, ActionResponse response)
        throws IOException, PortletException {
        ........
        response.setRenderParameter("zip-id", zip);
        ........
    }
}

Sample Application: Weather Map

Figure 2 shows the Weather and Map portlets. The Weather portlet sets the zip code, which is declared as a public render parameter. This parameter is supported by both Weather and Map portlets. Any change in the value of zip by Weather portlet is reflected during the render phase of both weather and map portlets.

Weather Map
Figure 2. Sample Application: Public Render Parameters

Using Aliases

Aliases can be used to coordinate between two portlet applications that use different public render parameter identifiers. Consider a WeatherPortlet that uses zip code as a public render parameter and a MapPortlet that uses pincode as a public render parameter. These two portlets cannot coordinate because the identifiers are different. By adding an alias in the portlet.xml file of one of the portlet applications, the portlets can coordinate with each other.

To add aliases for portlet public render parameters:

Add an alias (the same as qname of the public render parameter of WeatherPortlet) in the portlet.xml file of MapPortlet, as shown in the following code example.

Note: If you want to add an alias in WeatherPortlet, use the qname of the of the public render parameter of MapPortlet.

WeatherPortlet:




<portlet-app ......>
    <portlet>
        <portlet-name>WeatherPortlet</portlet-name>
        <display-name>WeatherPortlet</display-name>
        <portlet-class>com.sun.portal.portlet.WeatherPortlet</portlet-class>
         ........
        <supported-public-render-parameter>
             zip-id
        </supported-public-render-parameter>
    </portlet>
    <public-render-parameter>
        <identifier>zip-id</identifier>
        <qname xmlns:x="http://sun.com/params">x:zip</qname>
    </public-render-parameter>
</portlet-app>

MapPortlet:




<portlet-app ...>
    <portlet>
        <portlet-name>MapPortlet</portlet-name>
        <display-name>MapPortlet</display-name>
        <portlet-class>com.sun.portal.portlet.MapPortlet</portlet-class>
        ........
        <supported-public-render-parameter>
             pin-code
        </supported-public-render-parameter>
    </portlet>
    <public-render-parameter>
        <identifier>pin-code</identifier>
        <qname xmlns:x="http://sun.com/params">x:pincode</qname>
         
                        <alias xmlns:x="http://sun.com/params">x:zip</alias>
    </public-render-parameter>
</portlet-app>

                      

When the public render parameter zip-id is set in WeatherPortlet, it can be obtained as pin-code in MapPortlet.

public class WeatherPortlet extends GenericPortlet {


    public void processAction(ActionRequest request, ActionResponse response)
        throws IOException, PortletException {
        ........
        response.setRenderParameter("zip-id", "10025");
        ........
    }
}


public class MapPortlet extends GenericPortlet {

    public void doView(RenderRequest request, RenderResponse response)
        throws IOException, PortletException {
        ........
        String value = response.getParameter("pin-code"); // value is 10025
        ........
    }
}

For More Information