A JSR168-compliant implementation of inter-portlet communication

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

How it works

A portlet may have any number of inputs, and any number of outputs. It refers to these inputs and outputs using a local name in the portlet code (eg "person_name", "item_id"). Mappings of input/output names to global message boxes are defined externally to the portlet code - in the portlet preferences.

A message box can hold only one message at a time: sending a new message to it will overwrite the old one. The messages never expire, as we cannot know whether the interested portlets no longer need them (actually, this implementation stores them in the session, so when the user session expires they will be cleaned up). Messages can be any sort of Object, though it may be wise to make them Serializable.

A message input is mapped to a source message box, from which it reads its message.

A message output always sends its message to a local (namespaced to its own portlet instance) message box of the same name, but it may also publish it to any number of additional global message boxes.

Limitations

The current implementation is limited to messaging between portlets in the same portlet webapp. This is because the messages are stored in the session, and portlets in different webapps have different sessions (according to spec). If this is not good enough, you can have a go at coding an alternative message storage system.

Caching of portlet views is a problem: if you want the view of another portlet to update because an input message has changed, but the portlet is cached, the portal won't call its 'doView' and you have no way of forcing it to - that I know of, anyway(?). If you have enabled caching like this, I guess you'll have to add a 'refresh' link to the portlet so that the user can force a reload. However this isn't very user-friendly - another option which works is to disable view caching entirely by setting

<expiration-cache>0</expiration-cache>

for your portlets in portlet.xml. You will then need to ensure that your 'doView' is as lightweight as possible, as it will be called every time the page loads.

This is not critical, but something to be aware of: Portlets only 'register' their mappings when they first load. The provided mapping configuration form (for use in a portlet's Edit mode) lists message boxes available as sources or targets, but it will only be able to 'see' those that have already been registered in the current session's MessageCentre. So if you haven't visited a portlet's page, its message boxes won't appear yet in another portlet's Edit mode.

How to use it:

  • Add portlet-messaging.jar to your portlet webapp's WEB-INF/lib
  • Make sure you call MessageHelper.loadPrefs at the beginning of your portlet's doView
  • The portlet can read messages using MessageHelper.get(name), and send messages using MessageHelper.send(name). If you're sending messages, and want to avoid problems due to portlets loading out of order/in parallel on the page, make sure you send them in processAction - that way they'll be sent and ready by the time the other portlets get to doView.
  • Important step: configure the portlet's message mappings in its entry in portlet.xml. Each input has a Name (local, used in the portlet code), a SourceName, and a SourceNamespace. Each output has a Name (local, used in the portlet code), and any number of additional Publish destinations (which each consist of a PublishName and a PublishNamespace). Example settings for a portlet with one input and one output are shown below. Note that these are the defaults: as it is now, if you define a Publish target, and the user later tries to remove it, they won't be able to (although they would be able to re-map it).
    <portlet-preferences>
    	<preference>
    		<name>MsgInput1Name</name>
    		<value>author</value>
    	</preference>
    	<preference>
    		<name>MsgInput1SourceName</name>
    		<value>person</value>
    	</preference>
    	<preference>
    		<name>MsgInput1SourceNamespace</name> 
    		<value></value>
    	</preference>
    	<preference>
    		<name>MsgOutput1Name</name> 
    		<value>book</value>
    	</preference>
    	<preference>
    		<name>MsgOutput1PublishName1</name>
    		<value>title</value>
    	</preference>
    	<preference>
    		<name>MsgOutput1PublishNamespace1</name>
    		<value></value>
    	</preference>
    	<preference>
    		<name>MsgOutput1PublishName2</name>
    		<value>search_term</value>
    	</preference>
    	<preference>
    		<name>MsgOutput1PublishNamespace2</name>
    		<value></value>
    	</preference>
    </portlet-preferences>
    
  • Set up any wiring that is different from the defaults in portlet.xml:
    • If your portal structure will be fixed, and it allows you to directly set the preferences for each portlet instance somehow, you can do that to set up the wiring.
    • Or, write an Edit mode that lets the portlet user modify the inputs/outputs in the preferences. Once you've saved the preferences, reload the mappings by calling MessageHelper.setLoadedPrefs (to false) and then MessageHelper.loadPrefs
    • Or, if you want to use the full automatic wiring interface provided (see the screenshots):

What's the Portlet ID?

Lots of the functions and classes in this messaging system require a portlet ID. For the messages to get to the right places, we clearly need a unique ID for each portlet instance. However I haven't yet found a consistent way of retrieving a portlet instance id that works across different portals (if there is one, please tell me!). For example, I've seen a suggestion to add a portlet parameter containing an id in the portlet.xml - but that will end up being the same on all instances of that particular portlet, so you still can't distinguish between them. Some portals have their own functions for retrieving an instance id, but then you'd be locked to using that particular portal.

I've settled for randomly generating portlet instance id's, and storing them in the local portlet's session. The function to do this is MessageHelper.getPortletID - you can use this, or implement your own solution; as long as each portlet instance has a unique ID, it can be any String.

Alternative MessageCentre implementations

The provided MessageCentreImpl stores the messages in the session, which means that it can't be used for communication between different portlet applications.

However, the source is all here and if you want to implement a version of MessageCentre which stores the messages in a more globally accessible place, feel free - and I'd appreciate it if you want to send me a copy! You should be able to just make a class that implements MessageCentre, and modify MessageHelper.getMessageCentre to create an instance of your new class rather than MessageCentreImpl.



diagram of messaging library

Messaging model: Message Centre keeps track of all the mappings between local message names and global message names, allowing dynamic reconfiguration of message 'routes' by the administrator or the portal user.

3 messaging portlets

Three messaging portlets

multiple instances of the same portlets

Multiple instances of the same portlets: they have different mappings, defined in their parameters

wiring portlet: initial state

Dynamically mapped portlets: initial state

wiring portlet: all in Edit mode

Dynamically mapped portlets: all in Edit mode

wiring portlet: sent message from 1st to 2nd

Dynamically mapped portlets: sent message from 1st to 2nd

wiring portlet: sent message from 2nd to 3rd

Dynamically mapped portlets: sent message from 2nd to 3rd

wiring portlet: changed 3rd to read from author_name

Dynamically mapped portlets: changed 3rd to read from author_name

wiring portlet: 3rd now does search using author_name

Dynamically mapped portlets: 3rd now does search using author_name