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.
Part 1 - Overview and Coordination Between Portlets
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.
To provide coordination between portlets, the Java Portlet Specification, JSR 286, introduces the following mechanisms:
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());
}
}
........
}
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.
Figure 1. Sample Application: Eventing Map
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>
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
.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
.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
.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.)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"
........
}
}
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);
........
}
}
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.
Figure 2. Sample Application: Public Render Parameters
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
........
}
}