Skip to main content

Session, Session, who's got my Session?

Posted by wholder on February 2, 2005 at 2:11 PM PST
I started investigating JSR-168, the portlet specification, a few months back, as part of a larger project to convert a legacy MIS into a Java-based system running on Tomcat. I started by writing some test code to try and characterize how portlets and servlets interact, as I was curious how a portal container like Pluto would be able to work within the confines of the servlet API.
If you're not familiar with Pluto, it's the portlet container that's designed to work inside a servlet container like Tomcat and provide the specialized portlet API defined in JSR-168. The Pluto project provides a functional implementation of a portal, but Pluto really seems intended to be a component used inside more functional portal implementations, such as Jetspeed.
Other than the usual learning curve pains, writing portlets didn't feel all that different from writing servlets. Once I learned how to write a portlet.xml file and learned how to deploy the web app containing the portlet so the portal would use it, I could create portlets that would generate the HTML fragments needed to render the portlet's contribution to the portal's composite HTML page.
However, things started to get wonky when I tried to write a portlet that would render HTML containing an tag that would reference a servlet to dynamically render the image data. The various articles I'd read on portlets all suggested that I could easily use objects placed into the session to pass data from my portlet to the servlet that would render the image. However, after many attempts, it just couldn't get it to work...
Curious to understand why, I started to expand the scope of my test portlet and brought in a companion servlet, so I could examine the state of the session from both the portlet's and servlet's perspective. I discovered that, while I tended to think that my portlet resided in my web app, when running inside the Pluto portal, it was actually invoked from a servlet in the Pluto web app named PortletServlet. In effect, the Pluto deploy process had rewritten my web app's web.xml file to include this servlet declaration similar to this:
  <servlet>
    <servlet-name>TestPortlet</servlet-name>
    <display-name>TestPortlet Wrapper</display-name>
    <description>Automated generated Portlet Wrapper</description>
    <servlet-class>org.apache.pluto.core.PortletServlet</servlet-class>
    <init-param>
      <param-name>portlet-class</param-name>
      <param-value>com.nglm.fwk.sys.TestPortlet</param-value>
    </init-param>
    <init-param>
      <param-name>portlet-guid</param-name>
      <param-value>wfh.TestPortlet</param-value>
    </init-param>
  </servlet>

it also added a servlet-mapping tag like this:
  <servlet-mapping>
    <servlet-name>TestPortlet</servlet-name>
    <url-pattern>/TestPortlet/*</url-pattern>
  </servlet-mapping>

So, I'd discovered that my portlet was being called as if it was a part of the Pluto web app, not as part of the web app where the code actually resided. And, since the servlet API has this to say about web apps and sessions:

  HttpSession objects must be scoped at the application (or servlet context) level. The underlying mechanism, such as the cookie used to establish the session, can be the same for different contexts, but the object referenced, including the attributes in that object, must never be shared between contexts by the container. To illustrate this requirement with an example: if a servlet uses the RequestDispatcher to call a servlet in another Web application, any sessions created for and visible to the servlet being called must be different from those visible to the calling servlet. -- SRV.7.3  

which, in effect, meant that I should not expect to be able to pass objects to a servlet in my web app. Major bummer...

Now, I had actually wondered about this issue before when I noticed that the pluto web app defined a Tomcat tag with an attribute named "crossContext" set to "true". This attribute, which is outside the scope of the servlet API, is described like this in the Tomcat docs:

  crossContext: Set to true if you want calls within this application to ServletContext.getContext() to successfully return a request dispatcher for other web applications running on this virtual host. Set to false (the default) in security conscious environments, to make getContext() always return null.  

While the description didn't state that this attribute could grant access to session objects, it did seem reasonable to assume it might be a related factor. So, I added a element for my web app and set crossContext="true", too, hoping this would grant my servlet access to the session object added by my portlet. But, drum roll please..., no effect. My servlet still could not get access to the session object.

Over the next several days, I expanded on my tests and learned a few more things about how sessions a handled by Pluto running under Tomcat. For example, I learned that if I used a PortletRequestDispatcher to call a servlet and include() its output, the called servlet did have access to any objects placed in the application-scope session, as seen by the portlet. That is, if my portlet did this:
  PortletSession pSes = request.getPortletSession();
  pSes.setAttribute("ASCOPE", "Application-scoped value", PortletSession.APPLICATION_SCOPE);
  PortletRequestDispatcher dis = context.getRequestDispatcher("/TestServlet");
  dis.include(request, response);

then TestServlet could obtain the session object like this:
  Object sVal = request.getSession().getAttribute("ASCOPE");
however, if TestServlet was called via a request from the browser, the session object was not accessible. So, I was left with a question: did the designers of Pluto and of JSR-168 really intend to provide a way to pass session data from a portlet to a servlet where both reside in the same web app? Or, did they only really intend to make this work for servlets invoked via a PortletRequestDispatcher?
My assumption is that there needs to be some way to permit a portlet's HTML to reference servlets in the same web app so that the kind of function I originally set out to implement, fetching dynamically-generated image data, is a common operation in many web apps. So, IMO, it should be possible for portlets to do the same. For example, a let's say a portlet wants to display a bar chart created from information fetched from a database via JDBC. In my experience, the only way to implement this is to use a servlet to generate the data from, as is often convenient, parameters passed to the servlet in a session object. So, it had to be that I was missing some critical detail. But, where to find it?
A few days later I decided to do some intensive googling, and finally found clue to the mystery when I ran across this post in a Jetspeed-2 discussion group:
  http://www.mail-archive.com/jetspeed-user@jakarta.apache.org/msg15009.html
which talks about yet another mystery Tomcat "feature". This time it's an attribute named "emptySessionPath" which is intended to be added in the tag in Tomcat's server.xml file. Aha!
Unfortunately, I soon discovered that the emptySessionPath attribute was only available in versions of Tomcat in the 5.5.x series, which is not the version of Tomcat included with the Pluto RC1 releases, which was the version of Pluto I was using (Pluto RC2 does include Tomcat 5.5.4 but, due to an, as yet, undiagnosed ClassCastException, I've never been able to get RC2 to work with my test web app, even though it does run.) But, I was soon able to update to Tomcat 5.5.x in the RC1 release and, voila, my test portlet was finally able to work as I originally intended. So, to summarize what I've learned, if you need to pass session objects from a portlet to a servlet using Pluto as your servlet container, the following three conditions must all be true:

  1. You must run Tomcat 5.5.x, or later.
  2. You must set crossContext="true" in your web app's element.
  3. You must set emptySessionPath="true" in the element in Tomcat's server.xml file.

If you omit any one of these three steps, it won't work.

Postscript: since writing this entry I have managed to resolve the ClassCastException error mentioned in passing here (a long story which I may tell in another blog entry at a future time) and can now report that this same 3 step recipe works fine with Pluto RC2, too.
Related Topics >>