Sun Java System Portal Server 7.1 Developer's Guide

Chapter 15 Using Inter Portlet Communications

This chapter explains includes the following sections:

Developing Inter Portlet Communication Portlets

This section explains about the steps to develop Inter Portlet Communication (IPC) sample portlets. The topics include:

Inter Portlet Communication API

Portlets can communicate with each other even if they are in different web applications. It is assumed that all the sample portlets are on the same instance of a Portal Server. The Inter Portlet Communication API uses event generation and notification to convey the information or data among portlets. The event notification occurs for the portlets, which are registered themselves for listening to that particular event.

The IPC API is located in the com.sun.portal.portlet package. Portlets that are interested in receiving an event implements a single interface, PortletEventListener.

public interface PortletEventListener
{
	public void handleEvent(EventRequest ereq, EventResponse eres)
 throws PortletException;
}

You can use EventRequest to obtain the event name and event payload data. You can obtain Event payload data either by getting the event stream and reading from it or by calling getEventData() method which returns an Object and then casting it appropriately. If getEventData() is called after getting the event stream an IllegalStateException is thrown. Similarly, if getEventStream() is called after getEventData() an IllegalStateException is thrown.

public interface EventRequest() 
	{
		public String getEventName();
		public InputStream getEventStream();
		public Object getEventData();
	}

You can use EventResponse to set the render parameters, so that the information can be passed on to the render method after processing the event received in the handleEvent() method.

public interface EventResponse() 
	{
		public void setRenderParameters(Map parmMap);
		public void setRenderParameter(String key, String value);
		public void setRenderParameter(String key, String[] values);
	}

The portlets can generate events only from within the handleEvent() or processAction() methods. Event can be generated by instantiating PortletEventBroker and calling createEvent() method on it. The PortletEventBroker constructor throws an IllegalStateException if you call from methods other than the handleEvent() or processAction().

public class PortletEventBroker
{
	//Make sure that the call is coming from handleEvent()
	or from processAction()
	public PortletEventBroker(PortletRequest pr);

	//to create an event ...
	public PortletEvent createEvent(String eventName)
	throws EventNotRegisteredException;
}

You can then use the PortletEventBroker instance to create an event by calling the createEvent() method.

public interface PortletEvent 
	{
		public String getEventName();
		public OutputStream getEventStream();
		public void setEventData(Serializable s);
		public void fire();
	}

You can set the event data can on the event stream which can be obtained by calling getEventStream() method. Alternatively, event data can be set by calling setEventData() method. If after obtaining the event stream, attempt to call setEventData() throws the IllegalStateException exception. Similarly, after calling setEventData() an attempt to call the getEventStream() throws the IllegalStateException exception. The event is then fired by calling the fire() method.

Event Generation and Subscription

All the portlets which are interested in listening or generating an event must declare it in the sun-portlet.xml file as shown below:

<portlet>
	<portlet-name>NAME</portlet-name>
	.
	<!-- Other declarations -->
	.
	<events>
		<generates-event>NAME</generates-event>
		<generates-event>NAME</generates-event>
			.
			.
			.
		<consumes-event>NAME</consumes-event>
		<consumes-event>NAME</consumes-event>
			.
			.
			.
	</events>
</portlet>

If a portlet requests an event, which it has not declared in the sun-portal.xml file, an exception NotRegisteredException is thrown. Wildcards cannot be used for declaring the events that are generated. Portlets interested in consuming all the events can use wildcard character (*) as shown below:

<portlet>
	<portlet-name>NAME</portlet-name>
	.
	<!-- Other declarations -->
	.
	<events>
		<generates-event>NAME</generates-event>
		<generates-event>NAME</generates-event>
			.
			.
			.
		<consumes-event>*</consumes-event>
	</events>
</portlet>

Event Handling Life Cycle

The event cycle starts with the response to user interaction from the inside processAction method. These are Generation 1 events. These events are placed in the event queue by the Portlet Container and dispatched in the order they are created. The dispatching of the events continue, until all the events in the event queue are dispatched to appropriate portlets.

Dispatching of the events amount to calling the handleEvent methods of the appropriate portlets. Portlets can generate events in the handleEvent method which are Generation next events. If a portlet has subscribed to events which are generated in different generations, it will receive the events in proper order. It means that the handleEvent will be called with Generation ith event first and upon completion of that method, handleEvent will be called with event from Generation i+1.

Infinite Event Cycle Detection

Events are generated in response to the user interaction (Generation 1) or in response to other events (Generation next). This could lead to create more generations. To control the number of generations, the maxEventGenerations parameter in the desktopConfig.properties file can be configured for maximum number of generations of events per request. When the event creation exceeds the specified maximum number, a failure event, eventHandlingFailed will be sent to all the participating portlets.

Deterministic Behavior

If a portlet generates events X and Y in that order, then events X and Y are delivered to the portlets in that same order. If portlet A and B are interested in Event X, either A or B can get event X first. If portlets A and B are interested in Event X, and upon receipt of that event, generate events Y and Z respectively. If portlet C is interested in Event Y and Z, then portlet C can receive events Y and Z in any order.

Failure and Exception Handling

In case of failure, handleEvent() method may throw PortletException. Container will catch that exception and stop sending the events from the event queue. Container then sends another event called eventHandlingFailed to all the portlets participating in that particular interaction. Container does not take any action if the PortletException is thrown while processing eventHandlingFailed event. Portlets can not generate and send any events while handling the event eventHandlingFailed. The portlet developer has the responsibility to take appropriate action needed.

Inter Portlet Communication Sample Portlets

This section provides the location details, Installation information, and code examples of IPC sample portlets.

Location of IPC Sample Portlets

The IPC sample portlets are located at /opt/SUNWportal/samples/ipc. The portlet samples in ipc directory are divided into ipc1 and ipc2 sub-directories. This is to demonstrate eventing between portlets belonging to different web application and those belonging to the same web application. The interaction between the IPC Portlet samples is as follows:

Source code for the following samples is located at the /opt/SUNWportal/samples/ipc/ipc1/src directory.

Source code for the following samples is located at the /opt/SUNWportal/samples/ipc/ipc2/src directory.

JSP files for the portlets are located at:

Web container deployment description files are located at:

Installing IPC Sample Portlets

The information on overview of portlets and how to install them are provided in Chapter 11, Overview of the Portlets.

Code Examples

This section provides code examples for:

Code Example for Generator Portlet (ipc2)

package com.sun.portal.portlet.ipc2;

	import com.sun.portal.portletappengine.ipc.EventRequest;
	import com.sun.portal.portletappengine.ipc.EventResponse;
	import com.sun.portal.portletappengine.ipc.PortletEvent;
	import com.sun.portal.portletappengine.ipc.PortletEventBroker;
	import com.sun.portal.portletappengine.ipc.PortletEventListener;
	...

	/**
	* The PricePortlet participates in the InterPortletCommunication.
	* This generates the event that is consumed by two portlets
	* (a)ConsiderationPortlet which is in a different web application
	* (b)DecisionPortlet which is in the same web application
	* This also consumes the event generated by the DecisionPortlet.
	*/
		public class PricePortlet extends GenericPortlet implements
		PortletEventListener {
			...
			public void processAction(ActionRequest request,
			ActionResponse actionResponse)
				throws PortletException, java.io.IOException {
				PortletEventBroker peb = new PortletEventBroker(request);
				try {
					PortletEvent pe = peb.createEvent("priceChanged");
					String price = null;
					Object obj = request.getParameter("price");
					if (obj != null) {
						price = (String) obj;
					} else {
						price = "9999";
					}
					pe.setEventData(price);
					pe.fire();

					PortletEvent pe2 = peb.createEvent("priceChanged2");
					pe2.setEventData("700");
					pe2.fire();

					PortletEvent pe3 = peb.createEvent("priceChanged3");
					pe3.setEventData("800");
					pe3.fire();

				} catch (Exception e) {
						// Do nothing!
					}
       				actionResponse.setRenderParameters(request.getParameterMap());
				}
				...
			}

Code example for Listener Portlet (ipc1)

package com.sun.portal.portlet.ipc1;

import com.sun.portal.portletappengine.ipc.EventRequest;
import com.sun.portal.portletappengine.ipc.EventResponse;
import com.sun.portal.portletappengine.ipc.PortletEventListener;
...

/**
* The ConsiderationPortlet participates in the InterPortletCommunication.
* This consumes the event generated by the PricePortlet which is in a
* different web application.
*/
public class ConsiderationPortlet extends GenericPortlet{
   implements PortletEventListener ...
   public void handleEvent(EventRequest ereq, EventResponse eres) {
      String data = (String) ereq.getEventData();
      eres.setRenderParameter("price", data);
   }
   ...
	  }

Code Example for Listener Portlet (ipc2)

package com.sun.portal.portlet.ipc2;

import com.sun.portal.portletappengine.ipc.EventNotRegisteredException;
import com.sun.portal.portletappengine.ipc.EventRequest;
import com.sun.portal.portletappengine.ipc.EventResponse;
import com.sun.portal.portletappengine.ipc.PortletEvent;
import com.sun.portal.portletappengine.ipc.PortletEventBroker;
import com.sun.portal.portletappengine.ipc.PortletEventListener;
...

/**
* The DecisionPortlet participates in the InterPortletCommunication.
* This consumes the event generated by the PricePortlet which is in the
* same web application.
* This also generates the event that is consumed by the PricePortlet.
*/
public class DecisionPortlet extends GenericPortlet{
   implements PortletEventListener ...
   public void handleEvent(EventRequest ereq, EventResponse eres) {
       String name = ereq.getEventName();
       if (name.equalsIgnoreCase("priceChanged")) {
           String data = (String) ereq.getEventData();
           int price_data = Integer.parseInt(data);
           PortletEventBroker peb = new PortletEventBroker(ereq);
           if (price_data > 500) {
               try {
                   PortletEvent pe = peb.createEvent("highPriceEvent");
                   pe.setEventData(data);
                   pe.fire();
               } catch (EventNotRegisteredException e) {
                   e.printStackTrace();
               }
           } else {
               try {
                   PortletEvent pe = peb.createEvent("lowPriceEvent");
                   pe.setEventData(data);
                   pe.fire();
               } catch (EventNotRegisteredException e) {
                   e.printStackTrace();
               }
           }
           eres.setRenderParameter("price", data);
       } else if (name.equalsIgnoreCase("priceChanged2")) {
           eres.setRenderParameter("price2", name);
       } else if (name.equalsIgnoreCase("priceChanged3")) {
           eres.setRenderParameter("price3", name);
       }
   }
   ...
}

Code Example for sun-portlet.xml of ipc2.war

<portlet>
	<portlet-name>decisionportlet</portlet-name>
	<events>
		<generates-event>decisionMade</generates-event>
		<generates-event>highPriceEvent</generates-event>
		<generates-event>lowPriceEvent</generates-event>
		<consumes-event>priceChanged</consumes-event>
		<consumes-event>priceChanged</consumes-event>
		<consumes-event>priceChanged</consumes-event>
	</events>
</portlet>

<portlet>
	<portlet-name>priceportlet</portlet-name>
	<events>
		<generates-event>priceChanged</generates-event>
		<generates-event>priceChanged</generates-event>
		<generates-event>priceChanged</generates-event>
		<consumes-event>highPriceEvent</consumes-event>
		<consumes-event>lowPriceEvent</consumes-event>
	</events>
</portlet>

Code Example for sun-portlet.xml of ipc1.war

<portlet>
	<portlet-name>considerationportlet</portlet-name>
	<events>
		<consumes-event>priceChanged</consumes-event>
	</events>
</portlet>