The Source for Java Technology Collaboration
User: Password:



Wayne Holder

Wayne Holder's Blog

Session, Session, who's got my Session?

Posted by wholder on February 02, 2005 at 02:11 PM | Comments (11)

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 <IMG> 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 <context> 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 <context> 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 <connector> 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 <context> element.
  3. You must set emptySessionPath="true" in the <connector> 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.

Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • We spent a fair amount of time investigating jetspeed. The API is strong but we quickly found it would crash if just one portlet became corrupted. That was even trying to use their basic services w/o making our own portlets.

    Good findings on the emptySessionPath. I had similar fun w/ Tomcat on the RMI bug w/ blanks in the classpath. yuck

    Posted by: smartinumcp on February 03, 2005 at 08:37 AM

  • I have investigated this and ended up with a different understanding.

    Pluto uses cross-context dispatching to send requests to a Portlet in another webapp. The Pluto webapp needs to have cross-context set for this to work in Tomcat.

    The Servlet spec, srv 7.3 that you quoted, I read as saying that when Pluto does the cross context dispatch, the Portlet should see an HTTP session from it's own webapp, not from the Pluto webapp. I believe I have seen that Tomcat violates (or violated) this by sending in the session from Pluto.

    In the past, I've solved this by writing a little "tunnel" servlet, putting it in with Pluto in the Pluto webapp, which cross-context dispatches the request to my servlet. Then instead of invoking the servlet that resides in the portal's webapp directly, I invoke it via the Tunnel url. So it ends up getting cross-context dispatched the same way as the portlets, from the same Pluto webapp. In this way, whatever Tomcat does, the Servlet and Portlet get the same session.

    I don't understand why the portlet / servlet's webapp needs to be set to cross contex, since they do not do cross context dispatching. They are the target of cross context dispatching.

    I'm also not sure what Tomcat is doing now - if it sends the initial request's session over the cross-context dispatch, which seems agains the Servlet spec, or if it picks up the proper session in the target webapp.

    Finally, I'm totally confused about the emptySessionPath feature, and how that helps the portlet (cross-context dispatched via Pluto) / servlet (directly accessed in the portlet/servlet webapp) sharing problem.

    Posted by: ggolden on February 04, 2005 at 12:51 PM

  • ggolden: I share your confusion as to why all this voodoo is required to make passing session objects from portlets to servlets work. I can only theorize as to why Pluto works the way it does but, since Pluto's implementation is, in many ways, entangled with Tomcat's, it's possible that this is not how Pluto's designers intended this all to work. Instead, it may be a side effect of some other issue, or limitation in Tomcat. I'm still working to understand the big picture so I can perhaps answer these questions in the future. In the meantime, my intent here was just to point to a usable workaround. -- Wayne

    Posted by: wholder on February 07, 2005 at 04:48 PM

  • Have you encountered any Null Pointer Exceptions? The war seems to explored to the tomcat webapps directory fine, but then I try to access the portlet via the browser nothing displays. I maded the correct updates to the portletentityregistry.xml and pageregistry.xml

    tomcat logs show the following:

    2005-03-10 10:22:10 ApplicationDispatcher[/pluto] Servlet.service() for servlet jsp threw exception
    java.lang.NullPointerException
    at org.apache.pluto.portalImpl.aggregation.PortletFragment.service(PortletFragment.java:151)
    at org.apache.jsp.WEB_002dINF.aggregation.ColumnFragment_jsp._jspService(ColumnFragment_jsp.java:65)
    at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:94)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
    at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:324)

    Posted by: west241 on March 10, 2005 at 12:52 PM

  • WAYNE! Please do tell how you resolved the ClassCastException! I'm getting the same error on Tomcat 5.5 with a snapshot of Pluto RC3!

    If you can decipher it, here is a link to a screenshot of Eclipse. According to eclipse, my HermesPortlet object is a javax.portlet.Portlet object, yet a ClassCastException is still thrown!

    Thank you VERY much for your explanation! I will blog this too and link to you if you publish!

    Posted by: elliotm on April 20, 2005 at 10:29 PM

  • Ahh, relief. See PLUTO-101 . I had an extra copy of the Portlet API jar file laying around!

    See this OnLamp article on class loading which besides being informative has links to other resources as well.

    Posted by: elliotm on April 21, 2005 at 01:21 AM

  • Hello everybody,
    I don“t know if this is the right site to post this message,so sorry if so.
    I use Jahia as content manager and portal server. Inside it I have a porlet, Java WebMail Application. Unfortunately, the last version is not prepared to interact with Jahia context, so I had to change all the URLs and references to keep the context within Jahia (for example using encode.URL(..) and other things).

    After reading and posting I found that with binary data I shouldn't use encodeURL method. I should link directly to the servlet, but in this case, my session was lost (I used Tomcat 4) and the webmail application requires user to log in again.
    Now I changed to Tomcat 5.5.9 which, according to I've read, is able to share session, but I'm still with the same problem of Tomcat 4, so I think I miss something.

    These are the steps the porlet follows:
    1.- I click in the message i want to see and message.jsp page is called. This jsp page (if an attachment is included) calls a method of class X, which returns something like
    img border="0" src="/webmail/jwma?acton=message&todo=displaypart&number=0"
    2.- Jwma is the controller servlet and according to the parameters, its displaypart method should be called, but before exception is thrown because the servlet "thinks" that the user is not authentificated.

    Is is neccesary to pass the session from class X to controller servlet in some way?

    Any suggestion would be truly apreciated...
    Thank you very much in advance,
    Rgds,
    Miriam

    Posted by: myriam15 on July 07, 2005 at 03:33 AM

  • This article was very useful. I wanted to implement session data sharing between a Vignette Portlet session and a DWR/Ajax class.

    After following the steps outlined above, it just worked.... although upgrading to Tomcat 5.5.16 was not as seamless.. major changes to my server.xml :-(

    Appreciate the effort in putting this together.

    Posted by: gervasegallant on April 05, 2006 at 07:17 AM

  • Another solution for whom can't upgrade to tomcat 5.5 or any servlet container that support such feature, see my blog http://aitit.blogspot.com/2007/05/simple-way-to-make-dwr-able-to-see.html

    Posted by: davidchantf on June 12, 2007 at 09:12 AM

  • Thank You.

    Posted by: bubakhan on August 09, 2007 at 06:30 AM

  • "if you need to pass session objects from a portlet to a servlet "....

    Will the same hold true if you want to pass session objects from a servlet to a servlet ?

    Posted by: mores on December 26, 2007 at 08:09 AM





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds