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:
A MessageStore must be set up which is accessible to all portlets in any portlet application.
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):
EJBMessageStore - implementation of MessageStore, used by portlet application and configured in PortletMessaging.properties. This simply delegates to the remote EJB. Included in the messaging library, but if you want to adapt it you'll have to modify the connection details in its code and recompile.
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:
Reading the cookie: the PortletRequest doesn't provide us with an exact equivalent for HttpServletRequest.getCookies(). However, hopefully you will be able to get at the cookie using PortletRequest.getProperty("cookie"): I have only tried this on Jetspeed 1.6 so far.
Writing the cookie: Portlets can't set cookies, because they have no access to the page headers (which is where cookies are set). So there are 2 options I am aware of, both of which can only happen after the page finishes rendering:
JavaScript: have the portlet write out javascript which sets the cookie.
Servlet in IFRAME: have the portlet write out an IFRAME (probably invisible, zero size) which contains a servlet or JSP in your portlet application. This servlet or JSP can set the cookie.
Therefore, there are a few more problems/restrictions with usage:
You need to have a 'CookieSetter' portlet which appears only on the first page after login.
It's possible for cookies to stay around after a user's portal session has finished (e.g. if someone logs out of a portal, then a different user logs in using the same browser). You can deal with this case by having the CookieSetter portlet check the session to see if it's done its job yet, and if it hasn't it clears the cookie and sets a fresh one containing the new session ID.
As discussed above, cookies can only be set by the client, after the Render phase has completed, and until that has been set, there will not be enough information available for messaging portlets to initialise. Therefore no messaging portlets can appear before the CookieSetter portlet, nor should they be on the same page as it.
It's possible for people to set their client (browser) settings such that this method will not work at all. E.g. they can turn off cookies, or JavaScript, or some browsers may not support IFRAMEs.
Implementation:
Retrieval: CookieSessionIDRetriever will look for a cookie named "cookie_msg_session_id". Included in the messaging library.
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:
RequestAttributeSessionIDRetriever will look for the PortletRequest attribute "message.portal_session_id". If you can get your portal to populate the "message.portal_session_id" PortletRequest attribute, you can reuse this class to retrieve it. Included in the messaging library.
Jetspeed 1.6: The modification I made was to org.apache.jetspeed.aggregator.impl.PortletRendererImpl.buildRenderingJob() (from Jetspeed 2 M3, which is included with 1.6), which is packaged in jetspeed-2.0-M3.jar.
compiled into the modified jetspeed-2.0-M3.jar, which you should be able to use to replace your existing one (back up first!).
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.