Sun Java System Portal Server 7.2 Developer's Guide

Chapter 16 New Features in Portlet 2.0 (JSR 286)

This chapter describes new features in Portlet 2.0 (JSR 286) that are available in Portal Server 7.2, along with a sample application to demonstrate how to write portlets with these features.

This chapter includes the following sections:

Events

Events enable portlets to communicate with each other by sending and receiving events. An event is a life cycle 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 application 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 a event-definition element.

The portlet creates events using the setEvent() method during action processing. This will be processed by the portlet container after the action processing has finished. 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 only returns the local part of the event name. The value of the event must be based on the type defined in the deployment descriptor.

ProcedureTo Create Portlets That use the event Feature

  1. Declare the events in the portlet.xml file. To do this:

    1. Set the event definition at the portlet application level. This specifies the event name and the object type.


      Note –

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



      <portlet-app xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd /opt/SUNWportal/dtd/portlet.xsd" version="2.0">
      	<portlet>
      		<portlet-name>listportlet</portlet-name>
      		<portlet-class>com.sun.portal.portlet.touripc.ListPortlet</portlet-class>
      
      		.......<>
      	</portlet>
      	<portlet>
      		<portlet-name>detailportlet</portlet-name>
      		<portlet-class>com.sun.portal.portlet.touripc.DetailPortlet</portlet-class>
      
      		.......<>
      	</portlet><>
      	<portlet>
      		<portlet-name>weatherportlet</portlet-name>
      		<portlet-class>com.sun.portal.portlet.touripc.WeatherPortlet</portlet-class>
      
      		.......<>
      	</portlet><>
      	<portlet>
      		<portlet-name>mapportlet</portlet-name>
      		<portlet-class>com.sun.portal.portlet.touripc.MapPortlet</portlet-class>
      
      		.......<>
      		</supported-processing-event>
      	</portlet>
      	<event-definition>
      		<qname xmlns:x="http:sun.com/tourevents">
      			x:Tour
      		</qname>
      		<value-type>com.sun.portal.portlet.touripc.Tour</value-type>
      	</event-definition>
      </portlet-app>
      
      @XmlRootElement
      public class Tour implements Serializable {
            public Tour() {
         }
            private String name;
         private String description;
         private String guide;
         private String weathercode;
         private String mapcode;
      
         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;
         }
            public String getGuide() {
             return guide;
         }
      
         public void setGuide(String guide) {
             this.guide = guide;
         }
            public String getWeathercode() {
             return weathercode;
         }
      
         public void setWeathercode(String weathercode) {
             this.weathercode = weathercode;
         }
            public String getMapcode() {
             return mapcode;
         }
      
         public void setMapcode(String mapcode) {
             this.mapcode = mapcode;
         }
         }
    2. In the portlet section, specify the event name defined above for the portlets that you want to publish in this event.


      <portlet-app xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd /opt/SUNWportal/dtd/portlet.xsd" version="2.0">
      	<portlet>
      		<portlet-name>listportlet</portlet-name>
      		<portlet-class>com.sun.portal.portlet.touripc.ListPortlet</portlet-class>
      		...
      		<supported-publishing-event>
      			<qname xmlns:x="http:sun.com/tourevents">
      				x:Tour
      			</qname>
      		</supported-publishing-event>
      	</portlet>
      		...
      </portlet-app>
    3. In the portlet section, specify the event name defined above for the portlets that want to process in this event.


      <portlet-app xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd /opt/SUNWportal/dtd/portlet.xsd" version="2.0">
      	<portlet>
      		...
      		<portlet-name>detailportlet</portlet-name>
      		<portlet-class>com.sun.portal.portlet.touripc.DetailPortlet</portlet-class>
      		.....
      		<supported-processing-event>
      			<qname xmlns:x="http:sun.com/tourevents">
      				x:Tour
      			</qname>
      		</supported-processing-event>
      	</portlet>
      	<portlet>
      		<portlet-name>weatherportlet</portlet-name>
      		<portlet-class>com.sun.portal.portlet.touripc.WeatherPortlet</portlet-class>
      		.....
      		<supported-processing-event>
      			<qname xmlns:x="http:sun.com/tourevents">
      				x:Tour
      			</qname>
      		</supported-processing-event>
      	</portlet>
      	<portlet>
      		<portlet-name>mapportlet</portlet-name>
      		<portlet-class>com.sun.portal.portlet.touripc.MapPortlet</portlet-class>
      		.....
      		<supported-processing-event>
      			<qname xmlns:x="http:sun.com/tourevents">
      				x:Tour
      			</qname>
      		</supported-processing-event>
      	</portlet>
      	......
      </portlet-app>			
  2. Issue an event in the portlet that was specified as a supported-publishing-event event in the portlet.


    public void processAction(ActionRequest request, ActionResponse response)
               throws PortletException, java.io.IOException {
           Tour selectedTour = new Tour();
           String name = request.getParameter("name");
           String desc = request.getParameter("desc");
           String guide = request.getParameter("guide");
           String wc = request.getParameter("wc");
           String mc = request.getParameter("mc");
           selectedTour.setName(name);
           selectedTour.setDescription(desc);
           selectedTour.setGuide(guide);
           selectedTour.setWeathercode(wc);
           selectedTour.setMapcode(mc);
           QName qname = new QName("http:sun.com/tourevents", "Tour");
           response.setEvent(qname, selectedTour);
            .........
       }
    } 
  3. Process the event in the portlet that has been specified as a supported-processing-event event in the portlet.


    public class MapPortlet extends GenericPortlet {
            .........
    
       public void processEvent(EventRequest request, EventResponse response) {
           Event event = request.getEvent();
           logger.severe("MapPortlet got event");
           if(event.getName().equals("Tour")){
               Tour payload = (Tour)event.getValue();
               response.setRenderParameter("TourName", payload.getName());
               response.setRenderParameter("TourMap", payload.getMapcode());
           }
       }
            .........
    }
    
    public class DetailPortlet extends GenericPortlet{
            .........
         public void processEvent(EventRequest request, EventResponse response) {
           Event event = request.getEvent();
           if(event.getName().equals("Tour")){
               Tour payload = (Tour)event.getValue();
               response.setRenderParameter("TourName", payload.getName());
               response.setRenderParameter("TourGuide", payload.getGuide());
               response.setRenderParameter("TourDesc", payload.getDescription());
           }
       }
            .........
    }
    
    
    public class WeatherPortlet extends GenericPortlet {
            .........
    
      public void processEvent(EventRequest request, EventResponse response) {
           Event event = request.getEvent();
           if(event.getName().equals("Tour")){
               Tour payload = (Tour)event.getValue();
               response.setRenderParameter("TourName", payload.getName());
               response.setRenderParameter("TourCode", payload.getWeathercode());
           }
       }
            .........
    }

Sample Application

The following figure shows the Tour Listing, Tour Details, Tour Map, and Tour Weather Portlets that participate in the event. Clicking on any Tour Listing, triggers an event. This event is processed by the Tour Details, Tour Map, and Tour Weather Portlets to show the relevant information.

Figure 16–1 Sample Application for Eventing

Sample Application for Eventing

Public Render Parameters

In Java Portlet Specification 1.0 (JSR 168), 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.

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.

ProcedureTo Create Portlets That use the Public Render Parameters

  1. 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 xmlns="http://java.sun.com/xml/ns/portlet/
                 portlet-app_2_0.xsd"             
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"             
                 xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/
                 portlet-app_2_0.xsd
                 http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
                    
    
    id="myPortletApp" version="2.0">
    	<portlet>
    	...
    	</portlet>
    
    	<public-render-parameter>
    		<identifier>zip-id</identifier>
    		<qname xmlns:x="http://sun.com/params">x:zip</qname>
    	</public-render-parameter>
    
    </portlet-app>
  2. Specify the render parameter that the portlet would like to share in the portlet section.


    <portlet-app_2_0.xsd"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd
    http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd
    
    id="myPortletApp" version="2.0"
    <portlet>
    		<description>WeatherPortlet</description>
    		<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>
    		<description>MapPortlet</description>
    		<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>
  3. 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 req, ActionResponse res)
       throws IOException, PortletException {
        . . . 
               res.setRenderParameter("zip-id", zip);
         }
         . . .
    }

Sample Application

The following figure shows the Weather and Map portlets. The Weather portlet sets the zip 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 16–2 Sample Application for Public Render Parameter

Sample Application for Public Render Parameter

Portlet Filters

A portlet filter is a Java technology-based component that can be used to modify the content of the portlet request and portlet response before or after any life cycle method of the portlet. The concept of a portlet filter is same as that of a servlet filter. The only difference is that a servlet has only one request handling method, service() and therefore there is only one type of the servlet filter. A portlet on the other hand has four types of request handling methods and therefore there are four different types of portlet filters.

The portlet API defines the following interfaces for creating portlet filters:

Each of the above filter interface contains a single doFilter (*Request, *Response, FilterChain chain) method, which differs in the type of request and response object. For example, the doFilter() method in ActionFilter takes the code>ActionRequest and ActionResponse objects while the doFilter() method of RenderFilter takes instances of the RenderRequest and RenderResponse.

Each filter interface extends a common base interface called javax.portlet.filter.PortletFilter. This common base interface contains the following two methods:

A single filter class can provide filter functionality for more than one life cycle method. Also, a single filter can provide filter functionality for more than one portlet. Multiple filters can be associated with one life cycle method of a portlet. The doFilter() method of a portlet filter might create customized request and response objects by using the *RequestWrapper and *ResponseWrapper classes and by passing these wrappers to the doFilter() method of the FilterChain object.

ProcedureTo Write a Portlet Filter

  1. Write a filter class.

    A filter class should implement one or more of the above mentioned four interfaces and should provide a no argument public constructor. The filter class should also override the init() and destroy() methods of the javax.portlet.filter.PortletFilter interface.


    public class HitCounterFilter implements RenderFilter {
          private FilterConfig filterConfig = null;
          public void init(FilterConfig filterConfig) throws PortletException {
           this.filterConfig = filterConfig;
       }
          public void doFilter(RenderRequest renderRequest, RenderResponse renderResponse, FilterChain filterChain) throws IOException, PortletException {
              . . .
           StringWriter sw = new StringWriter();
           PrintWriter writer = new PrintWriter(sw);
           . . .
           writer.println();
           writer.println("===============");
           writer.println("The number of hits is: " +count);
           writer.println("===============");
           . . .
           filterChain.doFilter(renderRequest, renderResponse);
       }
          public void destroy() {
       }
       }
  2. Add the following XML fragments in the portlet.xml files after you write the portlet filter class.


    <portlet-app xmlns="http://java.sun.com/xml/ns/portlet/
                 portlet-app_2_0.xsd"             
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"             
                 xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/
                 portlet-app_2_0.xsd
                 http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
    id="myPortletApp" version="2.0">
    <portlet>
    ...
    	<filter>
    		<filter-name>HitCounterRenderFilter</filter-name>
    		<filter-class>com.sun.portal.portlet.filter
         .HitCounterFilter</filter-class>
    		<lifecycle>RENDER_PHASE</lifecycle>
    	</filter>
    
    	<filter-mapping>
    		<filter-name>HitCounterRenderFilter</filter-name>
    		<portlet-name>HelloWorld</portlet-name>
    	</filter-mapping>
    
    </portlet-app>

Sample Application

The following figure shows the number of times the sample portlet has been accessed is logged using a Filter. Access the portlet and check the application server log file. The log file shows the number of times the portlet has been accessed.

Figure 16–3 Sample Application for Portlet Filter

Sample application for Portlet Filter

Resource Serving

The resource serving feature enables a portlet to serve a resource. Portlets can create two kinds of resource links to serve requests:

The portlet can create resource URLs pointing back to itself in the following ways:

When an end user invokes such a resource URL the portlet container calls the serveResource() method of the portlet and renders the content returned by the serveResource call.

The serveResource() method can also be used to implement AJAX use cases by invoking the resourceURL() method through the XMLHttpRequest in client-side JavaScript code. The client-side code of the portlet is then responsible for inserting either the markup or update the page DOM in a non-disruptive manner for the other components on the page.

The presentation logic for the resource serving portlet looks as below and shows how to serve portlet fragments by using the serveResource method:


<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>
.........
<portlet:defineObjects/>
........
........

<portlet:resourceURL var="jsURL" id="js" escapeXml="false" />

/* Load Dojo library, if it hasn't already */
if (typeof dojo == "undefined") {
   /* build script tag */
   var script = document.createElement("script");
   script.src = "<%=renderResponse.encodeURL(jsURL.toString())%>;
        .........
}
        ......... 
<portlet:resourceURL var="invoiceURL" id="invoice" escapeXml="false" />

<script type="text/javascript">
/* Use Dojo.io.bind to asynchronously get invoice content */
function <portlet:namespace/>_loadInvoice(invoice) {
   /* If an invoice was selected */
   if (invoice != null && invoice != "null") {
        .........
       var bindArgs = {
           url: "<%=renderResponse.encodeURL(invoiceURL.toString())%>",
           method: "POST",
           content: querystring,
           handle: function(type, data, evt) {
               if (type == "error") {
                   /* Swap returned error message */
                   data = "<p style='color:red'>" + data.message + "</p>";

                }

                /* Swap returned data into div tag */
                var div = document.getElementById("<portlet:namespace/>_content");
                div.innerHTML = "";
                div.innerHTML = data;
            },
            mimetype: "text/html"
        };
        dojo.io.bind(bindArgs);
    }

};

The portlet class that serves the resource looks as below:


public class InvoicePortlet extends GenericPortlet {
      .........
      @Override
   public void serveResource(ResourceRequest request, ResourceResponse response) throws PortletException, IOException {
              response.setContentType("text/html");
       String resourceID = request.getResourceID();
       Locale locale = request.getLocale();
              if (resourceID.equals("invoice")) {
           String invoice = request.getParameter("invoice");
           if (invoice == null) {
               throw new PortletException("Required parameter, invoice, is missing.");
           } else {
               String path = "/html/" + invoice + ".html";
               String content = getContents(path, locale, true);
               PrintWriter writer = response.getWriter();
               writer.print(content);
           }
       } else if (resourceID.equals("js")) {
           String content = getContents(jsPage, locale, false);
           PrintWriter writer = response.getWriter();
           writer.print(content);
       }
   }
      .........
}

Sample Application

The following figure shows the Invoice Portlet. When the user selects a invoice number in the drop-down box, an asynchronous request (AJAX call) is made by the JavaScript client to the portlet. The serveResource() method of the portlet is invoked to serve the content.

Figure 16–4 Sample Application for Resource Serving

Sample Application for Resource Serving

Portlet Policy

Portlet Container 2.0 introduces a Portlet Policy that governs policies related to events, container events and public render parameters. The policy specifies how the events, container events, and public render parameters are distributed. It also specifies the maximum generation of events to prevent endless 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. It also provides the ability to enable/disable the container events.

The portlet-policy.xml file is located in PortalServer-Data-Directory/portals/portal-id/config. The schema is located at https://portlet-container.dev.java.net/xml/portlet-policy.xsd.

The portlet-policy.xml file looks like this:


<portlet-policy
	xmlns="https://portlet-container.dev.java.net/xml/portlet-policy.xsd"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://portlet-container.dev.java.net/xml/portlet-policy.xsd"
	version="1.0">

<event-policy>
	<event-distribution>VISIBLE_PORTLETS_ON_PAGE</event-distribution>
	<max-event-generation>3</max-event-generation>
</event-policy>

<container-event-policy>
	<event-distribution>ALL_PORTLETS</event-distribution>
	<max-event-generation>2</max-event-generation>
	<event>
		<name>login</name>
		<status>enabled</status>
	</event>
	<event>
		<name>logout</name>
		<status>enabled</status>
	</event>
</container-event-policy>

<public-render-parameter-policy>
	<public-render-parameter-distribution>VISIBLE_PORTLETS_ON_PAGE</public-render-parameter-distribution>
</public-render-parameter-policy>

</portlet-policy>

The above example shows that only visible portlets on the page will receive events and public render parameters. This also specifies that the maximum number of events permitted to be generated is two. This also specifies that the container events, login and logout are enabled. Currently, only login and logout container events are supported.

Any portlet interested in login event should specify the following in its portlet.xml. The processEvent() method of the portlet will be called when the user logs in.


<supported-processing-event>
	<qname xmlns:x="urn:oasis:names:tc:wsrp:v2:types">
		x:login
	</qname>
</supported-processing-event>

Any portlet interested in logout event should specify the following in its portlet.xml. The processEvent() method of the portlet will be called when the user logs out.


<supported-processing-event>
	<qname xmlns:x="urn:oasis:names:tc:wsrp:v2:types">
		x:logout
	</qname>
</supported-processing-event>