In Seam, in the pages.xml or mypage.page.xml files, you note that a given page requires the user to be logged in to view the page. It is a very easy way of handling simple security. What happens is if a user attempts to access a page with the login-required="true" attribute and they are not logged in, they are automatically redirected to your login page (as defined in your pages.xml). Once they login, they are automatically redirected back to the page they had attempted to access initially. Very elegant, and much easier than writing all that yourself.

There is however a “gotcha” you should be aware of in this flow. If you use the login page, or logic, to start a long running conversation (I do this because my User entity object has some large binary properties which I want lazily loaded), the post-login redirection will kill that conversation. In my case this led to lazy loading exceptions down the road.

It took me a while to figure out the cause, as it only happened much later in the application, and most of the time it worked (when I logged in purposefully from the main page), but sometimes would break (when I’d had an expired session and gone through the login auto-redirection flow). Eventually though I was able to track it down to those two scenarios.

What is happening is this: before the first redirection happens (to the login page), the Seam Redirect classes captureCurrentView method is called. This basically saves the information about the page you were trying to view (it’s a little more complex than that- being all Faces like and whatnot, but that’s the gist of it). This method creates a long running conversation in order to hold this state across the multiple pages. If there was no previous long running conversation to join, and it had to create a new conversation, it stores a conversationBegun flag of true.

Then you hit the login page, where the page (or code) would Begin a long running conversation, except you almost certainly have join=true, so it joins the existing long running conversation, which was created by the captureCurrentView method. This is the conversation your User entity was loaded up in.

After your login was successful, the Redirect classes returnToCapturedView method is called to send you back to where you’d tried to go initially.

This method has the following block:

  
if (conversationBegun) {
       Conversation.instance().end();
}

This ends the conversation you had loaded your User entity within, and when you hit your destination page, there is no long running conversation at all. Then, a few minutes later, it’s lazy loading exception time.

The downside is there isn’t a really clean fix that I am aware of yet.

The upside is there is a pretty simple hack: just add a <begin-conversation join="true" /> to all of your pages which have the login-required="true" attribute. This creates a new long running conversation before the Redirect does it’s work, so it in turn joins that conversation instead of creating a new one, the conversationBegun flag is false, and the returnToCapturedView method doesn’t end your conversation. It’s a hack, and you have to remember to add it to every page which requires a logged in state, but it does work.