Sharing information between IBM portlets and JSR 168 portlets with WebSphere Portal V5.1

Create a customized property sharing portlet service

The capability of portlets to exchange information (also called inter-portlet communication or cooperative portlets) is powered by the property broker in WebSphere Portal. The WebSphere Portal V5.1.0.1 property broker does not support inter-communication between IBM portlets and JSR 168 portlets because they run within separate portlet containers. This article shows you how to write a custom portlet service to enable legacy IBM portlets and JSR 168 portlets to share information as properties. You also see how to manage the life cycle of a shared information property in a distributed environment, using the dynamic WebSphere Application Server caching feature. To get the most out of this article, you should have a good understanding of Java portlet programming and a basic understanding of the WebSphere Portal portlet service feature. See Resources for links to information that can help you get this foundation.

Share:

Stefan Hepper, WebSphere Portal Programming Model Architect, EMC

Stefan HepperStefan Hepper is the responsible architect for the WebSphere Portal and Workplace programming model and public APIs. He co-led the Java Portlet Specification V1.0 (JSR 168) and is now leading the V2.0 (JSR 286) effort. Stefan received a Diploma of Computer Science from the University of Karlsruhe, Germany, and in 1998 he joined the IBM Böblingen Development Laboratory.


developerWorks Professional author
        level

Jerry Zheng, Solution Architect and Development Lead, WebSphere Commerce Enabled Portal, EMC

Jerry Zheng photoJerry Zheng is the solution architect and IBM Software Answers Network SME for the WebSphere Commerce Enabled Portal solution. He is also the development team lead for the WebSphere Commerce tools framework for Commerce Accelerator and Administrative Consoles. Jerry received a Master Degree of Computer Science from York University, Canada and a Master Degree of Economics from University of Guelph, Canada before joining the WebSphere Commerce development team at IBM Toronto Lab in 1999.



15 February 2006

Introduction

Portlets are Java-based Web components that process requests and generate dynamic content. This portlet-generated content, called a fragment, is defined by markup (such as HTML, XHTML, WML) which adheres to certain rules. Fragments can be aggregated to form a complete document called a portal page.

Portlets often need to communicate with each other in order to share information. The WebSphere Portal property broker provides the ability for portlets to send and receive properties between each other. Although they can be developed and deployed separately, these cooperative portlets can interact with each other in a seamless fashion to produce a result page for end users.

The WebSphere Portal property broker facilitates communication for portlets written to the same API which reside on the same or different pages (support for the later was added in V5.1.0.1). Cooperative portlets subscribe to the property broker by declaring properties (information they wish to share) in Web Services Definition Language (WSDL) format or using the programmatic API (IBM portlets only). Developers define a portlet to be either a source portlet (which sends a property) or a target portlet (which receives the property). After the administrator wires the source and target portlets together, the property broker matches the shared properties between these portlets and transfers these properties from the source to the target. For more information on cooperative portlets and the WebSphere Portal property broker, see the WebSphere Portal InfoCenter.

The property broker in WebSphere Portal V5.1 does not allow communication between IBM portlets and JSR 168 portlets because they run inside separate portlet containers. This limits the use of property broker in portals where legacy IBM portlets and standard JSR 168 portlets co-exist. Because J2EE prohibits the use of the HttpRequest object across portlet applications, a common work-around to the limitation is to use the PortletSession object to share information among portlets. However, this doesn't work either; PortletSession is scoped at either the portlet or portlet application context level, according to JSR 168 specification 1.0. IBM portlets and JSR 168 portlets simply can not be packaged within the same portlet application. In order to share information between IBM portlets and JSR 168 portlet, a custom solution is required.

This article explains how you can use the WebSphere Portal portlet service infrastructure to implement a custom portlet service to act as a passive property sharing service to exchange data between any portlets in different portlet applications across the portal. With this custom portlet service, you can:

  • Share information between portlets globally
  • Share information between portlets within a user session

This article also illustrates the use of the dynamic cache feature in WebSphere Application Server to manage the life cycle of stored objects within the service in a distributed environment.

You could choose to extend this custom portlet service for other purposes beyond information sharing. For example, you could use this service to cache any Java objects that are not appropriate to be saved within PortletSession object. For example, consider the weather portlet; you might not want to connect to the backend weather server every time a user refreshes the view or submits a new request because this operation can be very expensive. Instead, you could create a hash map of zip code and weather information, and save that information into the portlet service. Then, you would only need to connect to the backend server at some interval (say, every thirty minutes) to refresh the data. With this solution, all users of the portal can share the same weather information from the cached hash map, with much less performance overhead.

This article and the samples apply to IBM WebSphere Portal V5.1 (or higher) and to IBM WebSphere Application server V5.1 (or higher).

About the example

You can download the sample code for the custom portlet service and two example portlets (one IBM portlet and one JSR 168 portlet), and refer to it as you read the rest of this article.

Reviewing the portlet service feature

Portlet services are services which provide common functionality to portlets. WebSphere Portal supports two types of services: the default portlet services that ship with the product out-of-the-box and custom portlet services that you or someone else creates. Both IBM portlets and JSR 168 portlets can use these services.

  • JSR 168 compliant portlets use a JNDI lookup to retrieve a PortletServiceHome object, which is used to retrieve a portlet service implementation.
  • IBM portlets retrieve portlet services using the PortletContext.getService() method.

WebSphere Portal V5.1.0.1 provides dozens of default portlet services out-of-the-box. Some of the more commonly used ones are listed below. See the WebSphere Portal InfoCenter for more information on portlet services.

Content access service
com.ibm.portal.portlet.service.contentaccess.ContentAccessService

Lets a portlet access the Internet and enables the portal administrator to manage this access centrally (for example, set proxy servers and exclude specific Web sites).

Credential vault
com.ibm.portal.portlet.service.credentialvault.CredentialVaultService

Stores user IDs and passwords for back-end systems to facilitate providing a single sign-on experience for the portal user.

Dynamic UI manager
com.ibm.portal.portlet.service.dynamicui.DynamicUIManagementFactoryService

Launches dynamic portlets and pages.

Task manager service
com.ibm.portal.portlet.service.taskmanager.TaskManagerDelegateFactoryService
com.ibm.portal.portlet.service.taskui.TaskUIManager

Enables portlets to participate in BPEL-related workflows.

PUMA (Portal User Management Architecture) service
com.ibm.portal.um.portletservice.PumaHome

Enables portlets to access profiles of a user or group.

The portlet service framework also allows developers to write their own custom portlet service and register it in the Portal, so that all portlets can use it.


Creating the information sharing portlet service

The sample portlet service provides two methods for sharing information globally, and two for sharing information within a user session.

Defining the interface

The first step in creating a custom portlet service is to define the interface. The interface declares the public APIs that your portlet service provides.

Listing 1 shows the interface code for the CachedMapPortletService. The setGlobalData() and getGlobalData() methods provide support for sharing data globally (that is, all portlets across the portal and across different user sessions). The setSessionData() and getSessionData() methods are for sharing data within a user session.

Listing 1. CachedMapPortletService interface
package com.ibm.portal.sample.portletservice;

import javax.portlet.PortletSession;
import com.ibm.portal.portlet.service.PortletService;

public interface CachedMapPortletService extends PortletService
{
    public void setGlobalData(String key, Object value);
    public Object getGlobalData(String key);
    
    public void setSessionData(String key, Object value, PortletSession session);
    public Object getSessionData(String key, PortletSession session);
}

In order to make a portlet service accessible from IBM portlets, you also need another interface because the portlet services extension points are different for JSR 168 and IBM portlets.

Listing 2 shows the interface code for the same portlet service using the IBM specification. (You would not need this additional version of the interface if you are writing a custom portlet service for JSR 168 portlets only. We need if for this particular portlet service because we want to use it to exchange information between both IBM and JSR 168 portlets; therefore, it needs to be accessible to all portlets.)

Listing 2. CachedMapPortletServiceIBM – the IBM version of CachedMapPortletService
package com.ibm.portal.sample.portletservice;

import org.apache.jetspeed.portlet.PortletContext;
import org.apache.jetspeed.portlet.PortletSession;
import org.apache.jetspeed.portlet.service.PortletService;

public interface CachedMapPortletServiceIBM extends PortletService
{
    public void setGlobalData(String key, Object value);
    public Object getGlobalData(String key);
    
    public void setSessionData(String key, Object value, PortletSession session);
    public Object getSessionData(String key, PortletSession session);
}

Creating the service implementation

The next step is to write the implementation class of the service. This class must implement the portlet interface defined above as well as the PortletServiceProvider interface from the com.ibm.portal.portlet.service.spi package.

Listing 3. Service implementation class
package com.ibm.portal.sample.portletservice;

import com.ibm.portal.portlet.service.spi.PortletServiceProvider;

public class CachedMapPortletServiceImpl implements 
  CachedMapPortletService, PortletServiceProvider {
    
    private Map map = null;
    private static final String KEY_SEPARATOR = "_";
        
    // called by the portal when the service is initialized        
    public void init(Preferences servicePreferences) {
  	map = new HashTable(1000);
    }
    
    private String getSessionKey(javax.portlet.PortletSession session) {
    	return session.getId();
    }
        
    public void setGlobalData(String key, Object value) {
    	if (map == null) {
    		return;
    	} else {
    		map.put(key, value);
    	}
    }
    
    public Object getGlobalData(String key) {
    	if (map == null) {
    		return null;
    	} else {
    		return (Object) map.get(key);
    	}
    }
    
    public void setSessionData(String key, Object value, 
    javax.portlet.PortletSession session) {
    	if (map == null || session == null) {
    		return;
    	} else {
    		map.put(getSessionKey(session)  + KEY_SEPARATOR + key, value);
    	}
    }
    
    public Object getSessionData(String key, 
    javax.portlet.PortletSession session) {
    	if (map == null || session == null) {
    		return null;
    	} else { 
    		return (Object) map.get(getSessionKey(session)  + KEY_SEPARATOR + key);
    	}
    }
    	// convert IBM services calls to JSR 168  
    public void setSessionData(String key, Object value, 
		org.apache.jetspeed.portlet.PortletSession session, 
		org.apache.jetspeed.portlet.PortletContext context) {
    	setSessionData(key, value, 
			APIConverterFactory.getInstance().
				getPortletSession(session, context));
    }
    
    public Object getSessionData(String key,
					org.apache.jetspeed.portlet.PortletSession session, 
					org.apache.jetspeed.portlet.PortletContext context) {
    	return getSessionData(key, 
			APIConverterFactory.getInstance().
				getPortletSession(session, context));
    }
}

}

The initial HashTable object size is set to be 1000 in the init() method of the service. This object will hold all shared data across the portal as well as data used in each user session.

In the setSessionData() and getSessionData() methods, the session ID is used to scope shared data to a particular user session. This is possible because WebSphere Application server always assigns the same session ID to all Web applications associated with a specific user session.

Make the service accessible to IBM portlets

The final step is to make the service implementation class accessible from IBM portlets. To do this, you need to add the IBM service interface and implement all APIs defined within it. Then, you have a single implementation that is accessible for both interfaces. Because the service APIs defined in CachedMapPortletServiceIBM and CachedMapPortletService interface classes are the same, you can omit this step for CachedMapPortletServiceImpl class. However, if you decide to pass objects such as portlet session or portlet context to the service APIs, you need to have different method signatures for the IBM and JSR 168 versions of the interfaces. WebSphere Portal provides a utility class, APIConverterFactory, which you can use to wrap and convert certain objects from IBM portlets to JSR 168 API.


Adding distributed dynamic cache function to the portlet service

Now you have a fully functional information sharing portlet service which can be used to store and share any data objects in both global scope and session scope, but it still cannot be used in production yet. As you may have already suspected, the portlet service does not manage the life cycles of stored data objects. Once an object is stored in the service, it will stay there forever unless it's explicitly removed by the receiving portlet. This is problematic-- especially for the setSessionData() method-- because each user session will create an entry in the service class. Over time, the service class will grow out of control, causing serious memory contention in a production system. You can leverage the dynamic cache (dynacache) function provided by WebSphere Application Server to manage stored objects in a distributed environment.

WebSphere Application Server provides the DistributedMap interface as a simple interface for the dynacache. In addition to the default dynacache instance, developers can register and define multiple instances to cache data objects separately. Each instance can have its own set of properties including cache JNDI name, cache size, enable disk offload, disk offload location, flush to disk default, and use listener context.

First, you need to enable global dynacache service in the WebSphere Application Server administrative console. By default, this service is enabled. The steps listed below are for WebSphere Application Server V5.1. Steps for version 6.0 are similar; see the WebSphere Application Server Information Center for more information.

  1. Open the administrative console.
  2. Select Servers => Application Servers in the administrative console navigation tree.
  3. Click the WebSphere Portal server.
  4. Select Additional Properties => Dynamic Cache Service.
  5. In the Startup state field, select Enable service at server startup.
  6. Click Apply or OK.
  7. Restart WebSphere Application Server and WebSphere Portal Server.

Next, you need to register and configure a new dynacache instance for use by the portlet service. There are several methods for doing this; in this article, we chose to create a properties file. Simply create a file named distributedmap.properties under the was5_root\properties directory (or cacheinstances.properties under was6_root\properties directory) with the following content.

Listing 4. Distributed map properties file
cache.instance.0=/services/portal/sample/cachedmap
cache.instance.0.cacheSize=1000
cache.instance.0.enableDiskOffload=false
cache.instance.0.flushToDiskOnStop=false
cache.instance.0.useListenerContext=true
cache.instance.0.enableCacheReplication=true
cache.instance.0.replicationDomain=DynaCacheCluster
cache.instance.0.disableDependencyId=true

This creates a DistributedMap instance named cachedmap with a cache entry size of 1,000. Disk offload is disabled and use listener context is enabled. Flush to disk on stop is disabled and replication is enabled in a cluster environment. You can change the values to fit with your environment settings. See the WebSphere Application Server V6.0 InfoCenter for more information on these settings.

Finally, add the following code snippet to the init() method of the portlet service implementation class to make use of the distributed map object.

Listing 5. Service implementation class
public void init(Preferences servicePreferences) {
   	try {
    	InitialContext ic = new InitialContext();
   		map = (DistributedMap)ic.lookup("services/portal/sample/cachedmap");
   		System.out.println("CachedMapPortletServiceImpl - init - cache service 
   		services/portal/sample/cachedmap located successfully");
   	} catch (NamingException e) {
   		System.err.println("CachedMapPortletServiceImpl - init - can't find cache service 
   		services/portal/sample/cachedmap, creating non-cache map instead");
   		map = new HashTable(1000);
   	}
 }

This DistributedMap object stores both global and session data. If you have a large amount of data that needs to be saved in the cache instance, you might consider creating two cache instances and DistributedMap objects to separate the storage, so that relatively short-term session data will not swap out long-term global data; then, you avoid expensive operations of re-generating the global data.


Registering the service

To register the CachedMapPortletService into WebSphere Portal:

  1. If you have not already done so, download the sample code.
  2. Put CachedMapPortletService.jar in the wp_root/shared/app directory.
  3. Update the PortletServiceRegistryService.properties file in the wp_root/shared/app/config/services directory to register the new service, by appending the following section to the end of the file:
    # CachedMapService 
    jndi:com.ibm.portal.sample.portletservice.CachedMapPortletService =
    com.ibm.portal.sample.portletservice.CachedMapPortletServiceImpl 
    com.ibm.portal.sample.portletservice.CachedMapPortletServiceIBM =
    com.ibm.portal.sample.portletservice.CachedMapPortletServiceImpl

    This registers CachedMapPortletServiceImpl class as the implementation class for both IBM and JSR 168 portlet services.

  4. Restart the portal server.

Accessing the service

To access the CachedMapPortletService from IBM portlets, use the following:

Listing 7. Access the service from IBM portlets
PortletContext context = this.getPortletConfig().getContext();
CachedMapPortletServiceIBM service = 
(CachedMapPortletServiceIBM) context.getService(CachedMapPortletServiceIBM.class);

To access the CachedMapPortletService from JSR 168 portlets, you should first get the service home object inside the init() method; because the JNDI lookup is expensive, you only want to do it once.

Listing 8. Get the home object to facilitate JSR 168 access
public void init(PortletConfig config) throws PortletException{
        super.init(config);

        try {
            Context ctx = new InitialContext();
            Object home =
            ctx.lookup("portletservice/com.ibm.portal.sample.portletservice.CachedMapPortletService");
            if (home != null) {
            	CachedMapPortletServiceHome = (PortletServiceHome) home;
            }
        } catch (Exception ex) {
            // can't find service home
        }
}

After the service home object is found, you can access the service from the doView() or processAction() methods.

Listing 9. Access the service from JSR 168 portlets
CachedMapPortletService service = 
(CachedMapPortletService) CachedMapPortletServiceHome.getPortletService(CachedMapPortletService.class);

Testing the service

Now let's look at the sample to see how you can use the CachedMapPortletService to send and receive messages between portlets. Inside the sample download, you will find two sample portlet WAR files. The file names indicate their purpose: SampleIBM.war contains a sample IBM portlet, and SampleJSR168.war contains a sample JSR 168 portlet. Install and deploy both WAR files into your WebSphere Portal server and place them onto a page.

Figure 1. Sample IBM Portlet and JSR 168 Portlet – initial state
Figure 1. Both sample portlets – initial state

In this sample, you use the sample IBM portlet to send a text message to the sample JSR 168 portlet. Simply enter a text string in the input field of the sample IBM portlet and click Submit, you see the message that was received and displayed in the sample JSR 168 portlet, as shown in Figure 2.

Figure 2. Sample IBM Portlet and JSR 168 Portlet – send a message
Figure 2. Sample portlets after sending the message

Try the samples while logged in as different users concurrently from different machines or different browser sessions. You see that the text message is scoped to the user session; the sample code uses the setSessionData() and getSessionData() methods to transmit messages. If you want a particular message to be shared by all user sessions, you can change the code to use setGlobalData() and getGlobalDate() methods instead.

Although the sample only demonstrates the message type as text (String), you can use the CachedMapPortletService to send data objects of any type. You could also deploy the source and target portlets onto different pages.


Ideas for enhancing the service

The portlet service class introduced in this article is a useful complement to the WebSphere Portal property broker feature. It is helpful when your portal includes both IBM and JSR 168 portlets running at the same time and they need to communicate with each other; however, this portlet service is not meant to be a true implementation of the portlet messaging broker. Unlike property broker, this portlet service can only be used to receive a message in the portlet render phase, not in the portlet action phase, because there is no guarantee that the recipient portlet's action will always be executed after the sender portlet's action, where the message is composed and sent.

The CachedMapPortletService portlet service source code covered in this article is included in the sample download. You can use it as a starting point and then you enhance the portlet service to fit your specific needs.

For example, you might consider adding the feature to allow targeted portlet messaging. The portlet service code currently only has the broadcasting capability. As you can see from the service APIs, it doesn't allow the sender portlet to specify the name of the recipient portlet; therefore, all portlets (globally or those pertaining to a user session, depending on which APIs you use) can and will receive the message. To add the targeted portlet message capability, you can add extra APIs to take the portlet name as an additional parameter. Furthermore, if you want to scope the portlet messages to a per portlet window instance, you can do so by specifying a unique portlet window session key. See the article Caching data in JSR 168 portlets with WebSphere Portal V5.1 for more information on how to do this.

Another possible use for and enhancement of the portlet service is to use the portlet service as a caching broker to store and cache any data objects that are expensive to calculate or fetch from a back-end server. Depending on the user case, you can either set the cached data objects to be shared globally by all portlets and all end users, or set them for use by each user session.


Conclusion

Portlet messaging between IBM portlets and JSR 168 portlets is not supported by the WebSphere Portal V5.1 property broker. In this article, we developed a custom portlet service to enable IBM and JSR 168 portlets to exchange information-- that is, to send and receive messages with each other. You can use this service as a passive property sharing service to exchange information between portlets (or any other Java classes within the same JVM) across the portal, or within a unique user session. We also leveraged the dynamic cache feature from WebSphere Application Server to cache and manage the life cycles of shared data objects saved inside the portlet service. The sample portlets showed how to use the portlet service to send and receive messages.


Download

DescriptionNameSize
Code samplesCachedMapPortletService_Sample.zip  ( HTTP | FTP )18 KB

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into WebSphere on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere
ArticleID=103970
ArticleTitle=Sharing information between IBM portlets and JSR 168 portlets with WebSphere Portal V5.1
publish-date=02152006