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.
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.
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
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
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
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:
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. 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;
}
}
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);
....
}
To set the portlet to access the portal query parameters:
sun-portlet.xml
file looks like this. <portlet>
<portlet-name>PortletQueryParameterPortlet</portlet-name>
<portal-query-parameters>
<name>foo</name>
</portal-query-parameters>
</portlet>
http://localhost:8080/portletdriver?foo=bar
doView()
of PortletQueryParameterPortlet
, if you invoke request.getParameter("foo")
, it returns bar.
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>
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.
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);
}