A JSR168-compliant implementation of inter-portlet communication

Note: there is now a new version of the messaging library available.

This version of the messaging library supports communication between portlets in different portlet applications (that is, portlets have been deployed in separate .war files, whose definitions are in different portlet.xml's).

However, it is a lot easier if you can restrict yourself to communication between portlets in the same application (which the library will do by default). If at all possible, do that and you won't need to bother with the rest...

The reason for the complications is that portlets in different portlet apps do not have access to any common session.

There are 2 main issues that have to be dealt with to get cross context communication working:

  1. A MessageStore must be set up which is accessible to all portlets in any portlet application.
  2. The portlets need to be able to access an ID corresponding to the user's whole portal session.

Details of each, and some example implementations are given below.

You can plug in your chosen implementations: simply make the classes available in your portlet webapp, and configure them in this properties file: /WEB-INF/classes/message/PortletMessaging.properties.

    # MessageStore class to use
    # message.store=message.store.BasicMessageStore
    message.store=message.store.EJBMessageStore
    
    # SessionIDRetriever class to use
    # (only important for cross-context portlet communication)
    # session.id.retriever = message.retriever.NamespacedLocalSessionIDRetriever
    # session.id.retriever=message.retriever.RequestAttributeSessionIDRetriever
    session.id.retriever=message.retriever.CookieSessionIDRetriever

The MessageHelper will use the specified classes behind-the-scenes when necessary - you should not refer to them in your portlet code. So you can change the MessageStore/SessionIDRetriever without needing to recompile your portlets.

If this properties file is not present, the messaging library will default to single-application communication (i.e. not cross-context), with the MessageStore in the portlet session (as in version 1 of the library). Default classes used are message.store.BasicMessageStore and message.retriever.NamespacedLocalSessionIDRetriever.

If you implement a new MessageStore or SessionIDRetriever, and would like to make it available to others, I'd be very happy to add it to this site :) Particularly as the ones I have done are mostly proof-of-concept so far.

Thanks to Cameron Purdy and Ganesh Prasad, whose discussions were very helpful in working out how to go about implementing this.

Alternative MessageStore implementations

The MessageStore deals with both storage and access to the messages, and the mappings of local-global names. It is described by the MessageStore interface. See message.store.BasicMessageStore for an example implementation.

The MessageStore is kept within the portlet APPLICATION_SCOPE session, and therefore there is always one MessageStore object per portlet application. So for cross-context messaging, the MessageStore must not store the messages and mappings internally, but instead must be able to retrieve them from an external location.

This actual location where the messages and the mappings are stored must be accessible by any portlet application. Some portal-independent approaches would be:

As you can see, this will most likely add an external dependency to your portlets.

I have made a quick, proof-of-concept EJB Message Store which I've tested with an EJB deployed on JBoss. However this implementation isn't very efficient (the EJB simply delegates to a BasicMessageStore object), so for a production system you will probably want to code your own.

EJB Implementation (tested on JBoss):

Alternative SessionIDRetriever implementations

When a portlet goes to retrieve a message from the external MessageStore, it needs to be able to identify which user session it is participating in, to be able to retrieve the right messages. This session id needs to be available to all portlets, in all portlet applications. However, JSR168 doesn't give us an easy way of accessing an ID that serves this purpose, so we have to come up with workarounds.

For this one, you need to implement the SessionIDRetriever interface, which is itself quite simple: get the cross-context Session ID, somehow, from the provided PortletRequest.

Here are a few possible approaches I've tried - they work, but each has their own restrictions. You may need a different solution for your situation.

Use a Cookie

A cookie is theoretically available to all portlets in all portlet applications, and so can act as a shared source of information - a place to put the session id. (As we will see, the cookie is not a sensible place to put messages!) Firstly, there is the practical problem of how to access the cookie:

Therefore, there are a few more problems/restrictions with usage:

Implementation:

Hack your Portal

Yes, this is a portal-specific solution. But it may well be worth it considering how messy the non-portal-specific cookie solution is. Also, you can still plug in the cookie solution if you need to switch to a different portal later - it's just one line in the properties file, and doesn't affect your portlets' code.

There is already an ideal identifier for the user's portal session: the Portal's session id. For example, if you log into Jetspeed, that creates a session for the /jetspeed application. The problem is that the portlets have no way of accessing that session.

However, if your portal is open source, you can modify the code to pass on this session ID, in a PortletRequest attribute named "message.portal_session_id". I've done this with Jetspeed 1.6.

Implementation:

If you've got this far, you must be desperate for a cross-context solution ;-) Sorry that this probably isn't as simple to do as you'd like! Good luck, I'd like to hear how it goes.