Understanding the Java Portlet Specification 2.0 (JSR 286): Part 3, Extensions

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

Extensions

This article describes the additional features that are available as an extension in OpenPortal Portlet Container 2.x. These features are also available in Web Space server and Liferay Portal Server, as OpenPortal Portlet Container 2.x is consumed by them.

Coordination Between Portlets Across Pages

Eventing

By default, portlets can send and receive events with other portlets that are on the same page. This can be changed to enable portlets to send and receive events from other portlets that are on different pages. This can be done by adding the following property in portal-ext.properties in Web Space server and Liferay Portal 5.2.x:

portlet.event.distribution=ALL_PORTLETS

Public Render Parameters

By default, portlets can share render states with other portlets that are on the same page. This can be changed to enable portlets to share render states with other portlets that are on different pages. This can be done by adding the following property in portal-ext.properties in Web Space server and Liferay Portal 5.2.x:

portlet.public.render.parameter.distribution=ALL_PORTLETS

Preventing Infinite Loops in Eventing

The following property is used to specify the maximum number of events that can be generated from portlets to prevent infinite loops. If a portlet sends an event to other portlets, it is considered as one event generation. If the other portlets send events, that is considered as two event generations, and so on. The default value is 3.

portlet.event.max.generation=3

Shared Session Attributes

To support coordination between two portlets, JSR 286 (Portlet 2.0) has introduced events and public render parameters. If you want to pass data after serving the resource from one portlet to another portlet on a different page, events and public render parameters cannot be used. The shared session attributes feature has been added to support this use case.

The portlet can define shared session attributes that can be shared across portlet web applications. Shared session attributes are set, obtained, and removed using the APPLICATION_SCOPE portlet session methods. If you want to remove an attribute, set it to null. Shared attributes values must be serializable, and in addition they must have a valid JAXB binding.

The portlet must declare shared session attributes in the sun-portlet.xml deployment descriptor. At the application level, the portlet must define the shared session attribute definition with the <shared-session-attribute> tag. The shared session attribute definition must contain the attribute name and type. The type must be a fully qualified Java class name. The shared session attribute should uniquely identify the attribute and use the Java package naming standard and character restrictions.

To create portlets that use the shared session attributes feature:

  1. Declare the attributes to be shared by the portlet in the sun-portlet.xml file at the portlet application level. The shared session attribute definition must specify a name and an object type. Similarly, the portlet that wants to receive the shared session attribute must also specify the attribute in its sun-portlet.xml file.
    Note: The object must be serializable and must be annotated with the XML root element.
    PortletWebApplication1:
    
    
    <portlet-app-extension 
       xmlns="http://portlet-container.dev.java.net/xml/ns/sun-portlet.xsd" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:sunportal="http://portlet-container.dev.java.net/
          xml/ns/sun-portlet.xsd" 
       xsi:noNamespaceSchemaLocation="http://portlet-container.dev.java.net/
          xml/ns/sun-portlet.xsd"
       version="2.0">
    
     <portlet-name>PortletA</portlet-name>
        <shared-session-attribute>
            <name>com.foo</name>
            <value-type>com.foo.Address</value-type>
        </shared-session-attribute>
    
    </portlet-app-extension>
    
                           PortletWebApplication2:
    
    <portlet-app-extension 
       xmlns="http://portlet-container.dev.java.net/xml/ns/sun-portlet.xsd" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:sunportal="http://portlet-container.dev.java.net/
          xml/ns/sun-portlet.xsd" 
       xsi:noNamespaceSchemaLocation="http://portlet-container.dev.java.net/
          xml/ns/sun-portlet.xsd"
       version="2.0">
    
     <portlet-name>PortletB</portlet-name>
        <shared-session-attribute>
            <name>com.foo</name>
            <value-type>com.foo.Address</value-type>
        </shared-session-attribute>
    
    </portlet-app-extension>
    
    @XmlRootElement
    public class Address implements Serializable {
        private String street;
        private String city;
    
        public void setStreet(String s) {
            street = s;
        }
    
        public String getStreet() { 
            return street;
        }
    
        public void setCity(String c) { 
            city = c;
        }
    
        public String getCity() { 
            return city;
        }
    }
  2. Set the attribute in the portlet session at APPLICATION_SCOPE.
    PortletWebApplication1:
    
    
    public void serveResource(ResourceRequest request, ResourceResponse response) {
        ...
        Address sampleAddress = new Address();
        sampleAddress.setStreet("myStreet");
        sampleAddress.setCity("myCity");
    
        PortletSession session = request.getPortletSession();
        session.setAttribute(
             "com.foo", sampleAddress, PortletSession.APPLICATION_SCOPE);
        ....
    }
    
                              PortletWebApplication2:
    
    public void  doView(RenderRequest request, RenderResponse response) {
        ...
        PortletSession session = request.getPortletSession();
        Address sampleAddress = (Address)session.getAttribute(
             "com.foo", PortletSession.APPLICATION_SCOPE);
        ....
    }

Portal Query Parameters

To set the portlet to access the portal query parameters:

  1. Assume that the portal page has a portlet named PortletQueryParameterPortlet and its sun-portlet.xml file looks like this.
    <portlet>
    
        <portlet-name>PortletQueryParameterPortlet</portlet-name>
        <portal-query-parameters>
            <name>foo</name>
        </portal-query-parameters>
    </portlet>
  2. Access the portal using the following URL:
    http://localhost:8080/portletdriver?foo=bar
    In doView() of PortletQueryParameterPortlet, if you invoke request.getParameter("foo"), it returns bar.

XMLPortletRequest

XMLPortletRequest is a wrapper over XMLHttpRequest that shares the same syntax and semantics with XMLHttpRequest. If a portlet wants to update its UI asynchronously through the resource URL, it uses XMLPortletRequest instead of the XMLHttpRequest.

Here is an example of how to use XMLPortletRequest.

<script type="text/javascript"

    src="<%=renderRequest.getContextPath()%>/js/XMLPortletRequest.js">
</script>

<script>
    var portletRequest = new XMLPortletRequest("<portlet:namespace/>");
    portletRequest.onreadystatechange = function() {.....} 
    portletRequest.open("POST", "<portlet:resourceURL escapeXml='false' />"); 
    portletRequest.send("foo=" + bar);
</script>
Sample Application: XPR Invoice Ajax Portlet

The XPRInvoiceAjaxPortlet sample showcases how to use XMLPortletRequest. Using XMLPortletRequest drastically reduces the amount of code. Compare the View JSP page of InvoiceAjaxPortlet that does not use XMLPortletRequest to the View JSP page of XPRInvoiceAjaxPortlet that uses XMLPortletRequest to see the difference.

Client-Side Eventing

Consider a JSR 286 Java portlet that uses Ajax on the client side for actions. This typically calls the serveResource method of the portlet, which does not generate server-side JSR 286 events. Even if there is another JSR 286 Java portlet, it cannot leverage server-side eventing. Additionally, there might be other non-Java widgets on the page, and all of these will work in silo in the absence of client-side eventing. On the other hand, if there is a framework to generate and propagate events on the client, then this can benefit enormously. This feature tries to mimic JSR-286-based eventing on the client.

Consider a typical Ajax portlet that generates events. Although this portlet can be written in various ways, the example shown here tries to follow the pattern and conventions set by the JSR 286 specification for Java portlets.

Assume that there is a button that causes an action on the portlet. The code looks something like this example.

<input name="getbtn" id="getbtn" value="Get Something" type="button"

    onclick="<portlet:namespace>PortletObj.processAction(
         '<portlet:resourceURL id="i1"/>')"/>

Notice that there is a JavaScript object with a name that has been namespaced using a portlet tag to make it unique. Such a process action method for the JavaScript object can be implemented as shown here.

processAction : function(updateURL) {

    portletReq = new XMLPortletRequest("<portlet:namespace/>");
    portletReq.onreadystatechange = function() 
         {<portlet:namespace/>PortletObj.render();}; 
    portletReq.open("POST", updateURL); 
    portletReq.send("formData=" + someData); 
}

The callback is set to a render method on this portlet object as shown here.

render : function() {

    // process response
    var div = document.getElementById("<portlet:namespace/>portletData");
    div.innerHTML = ""; 
    div.innerHTML = portletReq.responseText;
    var qName = { uri : "http:example.com/myevents" , name : "SomeEvent" };
    var values = {
        value : [{"foo" : bar}]
    };
    portletReq.setEvent(qName, values); 
}

The most important thing to note is the event generation. As per the JSR 286 specification, setEvent could have been called in the processAction method. This would work if this was a simple case where some form data (which is ready at the time processAction is called), was to be passed on to the consumer of the event. If the event payload is more than the form data, and if it is actually a result of data returned by the Ajax call to the portlet, then triggering the event needs to be postponed until this data becomes available. That means the event data is available asynchronously when the callback is invoked. Or the event payload can even be returned as a JSON object or constructed as such. Hence the deviation from the JSR 286 specification to set the event in render.

The deployment descriptor for the portlet specifies the event as per the JSR 286 specification. The only difference is the value-type, which uses a dummy marker com.sun.portlet.ClientEvent.

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

    <portlet>
        ...
        <supported-publishing-event>
            <qname xmlns:x="http:example.com/events">x:SomeEvent</qname>
        </supported-publishing-event>
        ...
    </portlet>
    ....
    <event-definition>
        <qname xmlns:x="http:example.com/events">x:SomeEvent</qname>
        <value-type>com.sun.portlet.ClientEvent</value-type>
    </event-definition>
</portlet-app>

This tells the portlet container that the portlet uses client-side eventing. The wiring of the portlets is handled by the portlet container in the exact same way as it would have been done for the server-side eventing--that is, based on the deployment descriptor information. This happens transparently to the developer.

Thus, whenever the generator portlet calls setEvent, the event queue distributes the event object (payload) to all the portlets that support processing the event. This is also done in the deployment descriptor of the portlet as specified in the JSR 286 specification and as shown here.

<portlet>

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

The event processing portlet must implement a method processEvent, shown here. How the processEvent method is implemented is determined by the portlet developer.

processEvent : function (values) {

    this.render(values.value[0].foo);
}
For More Information