Skip to main content

skip to main content

developerWorks  >  Sample IT projects  >

The making of MetroSphere, Part 23: Send messages between portlets

developerWorks
Document options

Document options requiring JavaScript are not displayed

Discuss


Rate this page

Help us improve this content


Level: Introductory

Nicholas Chase, President, Chase and Chase, Inc.

23 Oct 2003

Sometimes, in the development of a portal, you need two portlets to talk to each other. For example, on MetroSphere, when the user clicks a topic in the Topic List portlet, the team needs the information to be sent not only to the Topic List portlet, but also to the Show Blog portlet. This portlet describes the process for sending messages between portlets and recording the information in the PortletSession object.

You'll need to have a working installation of any 4.x version of WebSphere Portal Server to follow along with this tip.

Editor's update

The Web site MetroSphere.com -- the online technical community discussed in this article -- is no longer live. However, the information and screen captures regarding the installation of IBM WebSphere Portal are still accurate and relevant.

What we want to accomplish

The MetroSphere site is a technology-focused community weblog, in which users can post information about interesting sites they've seen and add them to various categories, or topics. In Part 22 of this series, "Set topics -- custom attributes and user sessions," I showed you how to add a link to the topics portlet that would enable a registered user to toggle between showing all available topics and showing just his or her favorite topics. The link did nothing to affect the entries shown in the Show Blog portlet, however, so now we're going to remedy that.

What you're going to do is alter the link action for both the toggle links and the topic links themselves so that in addition to setting session information for the Topic List portlet, the action sends a message to the Show Blog portlet telling it what topic or topics to display. You'll then make changes to the Show Blog portlet to receive the message, decode it, and store the information in a session value.



Back to top


About sessions

If you've been following along with this series, you may remember that in Part 22 you created a toggle link that set an attribute for the session specifying which set of topics should be shown, as in Listing 1:


Listing 1: Setting a session attribute for the preferred topics
...
  private void setPreftopics(PortletRequest request, String topicState){
      PortletSession session = (PortletSession)request.getSession(false);
      if (topicState.equals("showpref")) {
        session.setAttribute("showtopics",  
                (String)request.getUser().getAttribute("favoriteTopics"));
        session.setAttribute("topicstate", "showpref");
      } else {
        session.setAttribute("showtopics", "-1");
        session.setAttribute("topicstate", "showall");        
      }
  } 
...
  public void actionPerformed (ActionEvent event)  {
      PortletAction portletAction = event.getAction();
      PortletRequest request = event.getRequest();
      
      if (portletAction instanceof DefaultPortletAction)
      {
          DefaultPortletAction action = (DefaultPortletAction)portletAction;
       
          if (action.getName().equals("showpref")){
              setPreftopics(request, "showpref");
              request.getSession().setAttribute("showtopic", "-1");
          } else if (action.getName().equals("showtopic")){
              setPreftopics(request, 
                     (String)action.getParameters().get("showTopicId"));
              request.getSession().setAttribute("showtopic", 
                     (String)action.getParameters().get("showTopicId"));
          } else {
              setPreftopics(request, "showall");
              request.getSession().setAttribute("showtopic", "-1");
          }
      }
  } 
...

Now, it may seem that this should be enough to make the information available to the Show Blog portlet. After all, a session is a session, right? You're even obtaining the session from the request object, so it makes sense that there should be only one.

Let's test that hypothesis. Add a session attribute to both the Topic List and Show Blog portlets. At the top of the doView() method, check the value and add a link to set it:


Listing 2: Displaying a message
...
public class TopicList extends PortletAdapter implements ActionListener {

...
  public void doView (PortletRequest request, PortletResponse response)
     throws PortletException, java.io.IOException
  {            
    PortletSession session = request.getPortletSession(false);

    if (session == null){
       response.getWriter().print("<p>No session yet</p>");
    } else {     
       String clickTime = "";
       if (session.getAttribute("clickTime") == null){
          clickTime = "Variable not set yet";
       } else {
          clickTime = (String)session.getAttribute("clickTime");
       }
       response.getWriter().print("<p>"+clickTime+"</p>");

       PortletURI clickTimeURI = response.createURI();
       DefaultPortletAction clickAction = new DefaultPortletAction("clickTime");
       clickTimeURI.addAction(clickAction);
       response.getWriter().print("<p><a href=\""+
              clickTimeURI.toString()+"\">Set session value</a></p>");
    }

    PortletContext context = getPortletConfig().getContext(); 
    context.include(viewJsp, request, response);    
...
  }

First you're checking to see whether there is a session. If there is, you're checking for a value of the clickTime attribute, and if it doesn't exist yet, you're giving it a value that says so. If it does exist, you can pull it out of the session object and cast it as a String value -- all attributes are retrieved as Objects. In either case, you output the value, and then create a link with an action named clickTime that will automatically set the session attribute to the current time.

To actually set the time, you'll have to make a change to the actionPerformed() method:


Listing 3: Setting the attribute
...
  public void actionPerformed (ActionEvent event)  {
      PortletAction portletAction = event.getAction();
      PortletRequest request = event.getRequest();
      
      if (portletAction instanceof DefaultPortletAction)
      {
          DefaultPortletAction action = (DefaultPortletAction)portletAction;
       
          if (action.getName().equals("clickTime")){
              java.util.Date now = new java.util.Date();
              request.getSession().setAttribute("clickTime", now.toString());
          } else if (action.getName().equals("showpref")){
              setPreftopics(request, "showpref");
              request.getSession().setAttribute("showtopic", "-1");
          } else if (action.getName().equals("showtopic")){
              request.getSession().setAttribute("showtopic", 
                     (String)action.getParameters().get("showTopicId"));
              setPreftopics(request, (String)action.getParameters().get("showTopicId"));
          } else {
              setPreftopics(request, "showall");
              request.getSession().setAttribute("showtopic", "-1");
          }
      }
  } 
    
}

The change itself is actually fairly straightforward; if it's the clickTime action that's been clicked, you create a new Date object and set it as the value of the clickTime attribute.

You can make exactly the same changes in ShowBlog.java, but don't forget to retrieve the session, if you're not doing so already.

Once you've added this test to both portlets, update the application and load the blog page in your browser. Both portlets should show that the variable hasn't been set yet. Click the link in the Topic List portlet and notice that the session variable for the Show Blog portlet still isn't set.


Figure 1. Viewing one variable

At this point some of you may be crying foul. After all, there's no guarantee of the order in which portlets are processed, so maybe the Show Blog portlet was just processed first, even though it's later on the page. Well, no. If you reload the page, you'll find that the information still doesn't show in the Show Blog portlet. If you click the link in the Show Blog portlet, you'll find that each one shows a different time.


Figure 2. Viewing both variables

The question is, why? The answer has to do with the architecture of a portlet application and how it interacts with sessions. While there is only a single HttpSession for a request, there is actually a PortletSession for each concrete portlet instance on the page. In fact, associating the portlet with the user session is what defines a concrete portlet instance. This means that an attribute set on a PortletSession in one portlet will not be available from the PortletSession in another portlet.

So how do you get information from one portlet to another? One way is to use portlet messaging to send information from one portlet to another.



Back to top


Event processing

Before I move on to the actual creation and receipt of a message, take a look at how messages are processed from an architectural standpoint.

When the portal receives an HTTP request, in most cases there are one or more portlets that need to be processed. In a servlet, all processing is done within the service() method, but with a portlet, processing is broken up into two phases. First, any events such as mouse clicks and messages are processed, and then each of the portlet service() methods (which include doView(), doEdit(), and so on) are processed.

Events are processed in a specific order:

  1. Action events
  2. Message events
  3. Window events

First the action events, generated when the user clicks a link or submits a form, are executed. Because they are processed first, the actionPerformed() method is an ideal place -- and in fact, virtually the only place -- to send a message. Next, the messages are delivered and processed by each recipient. Finally, any window events, such as the minimization of a portlet within the page, are processed.

This order is important. Because window events are processed after actions and messages, you couldn't, say, have a portlet send a message to another portlet when it's minimized. Any messages sent after the message event phase will be discarded. Note, however, that a portlet can send a message when it receives one, because the server is still in the "message event" phase.



Back to top


Sending a message

Start by creating a message in the Topic List's actionPerformed() method:


Listing 4: Receiving the messages
...
public class TopicList extends PortletAdapter implements ActionListener {

...

  public void actionPerformed (ActionEvent event)  {
      PortletAction portletAction = event.getAction();
      PortletRequest request = event.getRequest();
      
      String messageText = "";
      if (portletAction instanceof DefaultPortletAction)
      {
          DefaultPortletAction action = (DefaultPortletAction)portletAction;
       
          if (action.getName().equals("clickTime")){
              java.util.Date now = new java.util.Date();
              request.getSession().setAttribute("clickTime", now.toString());
          } else if (action.getName().equals("showpref")){
              setPreftopics(request, "showpref");
              request.getSession().setAttribute("showtopic", "-1");
              messageText = "TOPICMSG:showpref:"+getPreftopics(request);
          } else if (action.getName().equals("showtopic")){
              request.getSession().setAttribute("showtopic", 
                    (String)action.getParameters().get("showTopicId"));
              setPreftopics(request, 
                    (String)action.getParameters().get("showTopicId"));
              messageText = "TOPICMSG:showtopic:"+
                    (String)action.getParameters().get("showTopicId");
          } else {
                setPreftopics(request, "showall");
                request.getSession().setAttribute("showtopic", "-1");
                messageText = "TOPICMSG:showall";
          }
      }
      try {
          DefaultPortletMessage dpm = new DefaultPortletMessage(messageText);
          getPortletConfig().getContext().send("Show Blog", dpm);
      } catch ( AccessDeniedException ade ) {
          PortletLog pLog = getPortletLog();
          pLog.error( "MessageSenderPortlet: error sending message");
      }
  }    
}

You start by creating the message text itself. First you indicate that it is, in fact, a topic message. Right now, that's the only kind you're sending, but you might get to the point where you need to distinguish between types. Next, you indicate whether you want to show the preferred topics, all topics, or whether the user has clicked on a specific topic, in which case you also add the particular topic.

The message itself is a DefaultPortletMessage, sent by the PortletContext object's send() method. Notice that you've specified a particular portlet, Show Blog. This is the portlet name, as defined by the portlet-name element within the portlet-app definition in the portlet.xml file:


Listing 5: The portlet.xml file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE portlet-app-def PUBLIC "-//IBM//DTD Portlet Application 1.1//EN" 
                      "portlet_1.1.dtd">
<portlet-app-def>
   <portlet-app uid="DCE:c334a120-8107-1211-0000-002b7dd7b90c:1">
      <portlet-app-name>BloggingSystem application</portlet-app-name>
...
      <portlet id="Portlet_3" href="WEB-INF/web.xml#Servlet_5" 
                  major-version="1" minor-version="0">
          <portlet-name>Show Blog</portlet-name>
          <cache>
              <expires>0</expires>
              <shared>no</shared>
          </cache>
          <allows>
              <maximized/>
              <minimized/>
          </allows>
          <supports>
             <markup name="html">
                <view/>
             </markup>
          </supports>
      </portlet>
...
   </portlet-app>
   <concrete-portlet-app uid="DCE:c334a120-8107-1211-0000-002b7dd7b90c:1.1">
      <portlet-app-name>BloggingSystem application</portlet-app-name>
...
      <concrete-portlet href="#Portlet_3">
         <portlet-name>Bloggingsystem Show Blog portlet</portlet-name>
         <default-locale>en</default-locale>
         <language locale="en">
             <title>Blog Entries</title>
             <title-short></title-short>
             <description></description>
             <keywords></keywords>
         </language>
      </concrete-portlet>
...
   </concrete-portlet-app>
</portlet-app-def>

The code in Listing 4 sends the message with the topic information to the Show Blog portlet, but in actuality, you want to send the message to any portlet on the page that may need it. To do that, you can just omit the portlet name:


Listing 6: Broadcasting a message
...
      try {
          DefaultPortletMessage dpm = new DefaultPortletMessage(messageText);
          getPortletConfig().getContext().send(null, dpm);
      } catch ( AccessDeniedException ade ) {
          PortletLog pLog = getPortletLog();
          pLog.error( "MessageSenderPortlet: error sending message");
      }
  } 
}

In this case, the message will be received by any portlet that is on the same page as the sending portlet -- as long as they are both part of the same portlet application.



Back to top


Receiving a message

To receive the message, you need to implement the MessageListener interface and the messageReceived() method:


Listing 7: Receiving the message
package com.metrosphere.blog;

import org.apache.jetspeed.portlet.*;
import org.apache.jetspeed.portlets.*;
import org.apache.jetspeed.portlet.event.*;

public class ShowBlog extends PortletAdapter implements MessageListener, ActionListener{

  public void init(PortletConfig portletConfig) 
    throws UnavailableException
  {
    super.init( portletConfig );
  }
  
  protected static final String viewJsp = "/WEB-INF/html/showblog.jsp";
  protected static final String viewEntryJsp = "/WEB-INF/html/showblogentry.jsp";

  String outMessage = "not set";

  public void messageReceived(MessageEvent event) {

    DefaultPortletMessage message = (DefaultPortletMessage)event.getMessage();
    String messageText = message.getMessage();
   
    outMessage=messageText;        

  }

  public void doView (PortletRequest request, PortletResponse response)
     throws PortletException, java.io.IOException
  {

      response.getWriter().print(outMessage);

      PortletContext context = getPortletConfig().getContext();
      if (request.getAttribute("entrytoshow") != null){
...

Here you're simply retrieving the DefaultPortletMessage object from the message event, and then retrieving the text that makes up the actual message. At this point, you just want to make sure the message has been received, so assign it to a String variable, and then print the variable from within the doView() method. (Note that this is not an ideal way of passing information in the actual application, because only one copy of the portlet runs within the server, so the outMessage variable effectively becomes a class variable. For now, though, you can use it to see what's happening.)

Update the application, and then click a topic or the toggle link. You should see the message displayed at the top of the Show Blog portlet.


Figure 3. Viewing the message



Back to top


Using the message

Now that you have the message, it's time to put it to good use. Because of the way you structured the BlogBean object, all you need to do is feed the appropriate topicId values to the loadBlog() method once you decode it:


Listing 8: Evaluating the message
...
  public void messageReceived(MessageEvent event) {
    DefaultPortletMessage message = (DefaultPortletMessage)event.getMessage();
    String messageText = message.getMessage();

    PortletRequest request = event.getRequest();
    
    if (messageText.startsWith("TOPICMSG:")){
        messageText = messageText.substring(9, messageText.length());
        if (messageText.startsWith("showtopic:")){
            messageText = messageText.substring(10, messageText.length());
            request.getSession().setAttribute("showtopic", messageText);
            request.getSession().setAttribute("topicState", "showtopic");
        } else if (messageText.startsWith("showpref:")){
            request.getSession().setAttribute("topicState", "showpref");
            messageText = messageText.substring(9, messageText.length());
            request.getSession().setAttribute("showtopic", messageText);
        } else if (messageText.equals("showall")){
            request.getSession().setAttribute("topicState", "showall");
            request.getSession().setAttribute("showtopic", "-1");
        }
    }
  }

  private String getPreftopics(PortletRequest request){
      PortletSession session = (PortletSession)request.getSession(false);
      if (session == null){
          return "-1";
      } else {
          String showtopics = (String)session.getAttribute("showtopic");
          if (showtopics == null){
              return "-1";
          } else {
              return showtopics;
          }
      }
  }

  public void doView (PortletRequest request, PortletResponse response)
     throws PortletException, java.io.IOException
  {

...
      BlogBean blogBean = new BlogBean();
      blogBean.loadBlog(getPreftopics(request));
        
      while (blogBean.next()) {
...

Note that you're using the getPreftopics() method you created for the Topic List portlet as a matter of convenience in terms of checking for an existing session. Because we've now used it twice, you might move it into the com.metrosphere.util package for easier maintenance.

Update the application on the portal and click some of the links in the Topic List portal. You should see changes to the set of entries in the Show Blog portlet.



Back to top


Summary

The architecture of most sites requires one or more portlets to communicate with each other. Because sessions are individual to each portlet, you can't use them to move information from one portlet to another. Instead, you can use the action phase of one portlet to send a message either to another portlet or to any other portlet within the page that is part of the same portlet application.



Resources



About the author

Nicholas Chase

Nicholas Chase, a Studio B author, has been involved in Web site development for companies such as Lucent Technologies, Sun Microsystems, Oracle, and the Tampa Bay Buccaneers. Nick has been a high school physics teacher, a low-level radioactive waste facility manager, an online science fiction magazine editor, a multimedia engineer, and an Oracle instructor. More recently, he was the Chief Technology Officer of Site Dynamics Interactive Communications in Clearwater, Florida, and is the author of four books on Web development, including XML Primer Plus (Sams).




Rate this page


Please take a moment to complete this form to help us better serve you.



 


 


Not
useful
Extremely
useful
 


Share this....

digg Digg this story del.icio.us del.icio.us Slashdot Slashdot it!



Back to top