A JSF Session Scope Custom Solution
As a software consultant, I very rarely end up in a situation where I am asked to implement something new from scratch, and even rarer is the request to come in and enhance an existing piece of code that is well-designed and beautifully written. The request I received on my last project is much more the norm:
“We have several applications written in JSF using session scope. Last release we added an application that can be used to launch multiple instances of any or all of these session scoped apps. But our problem is that multiple instances of the same applications interfere with each; they seem to be sharing data. Please help us.”
Just in case the problem here doesn’t leap out the web page and grab you, just yet, here is the problem in a nutshell:
Web applications can be designed so that everything you want the server to know is passed on every request. For example, in a user registration application you could be prompted for name, user id, and password on one page and then prompted for security questions on the next page along with a submit button. The submit button is going to have to give the web application all of the registration information needed to register a user, so all the data from the first page had to be passed to the second page and then combined with the second page prompts on the submit. As you can easily imagine, a shopping application that may have hundreds of pages would get very complicated passing around your shopping cart on every page until you clicked “checkout”. This is where a session comes in. The shopping cart is a great metaphor for session as it is the thing that holds all YOUR stuff until you are ready to checkout. “YOUR” is emphasized because each web user gets their own cart/session.
The problem above is that the applications in question were written to use session, so each user gets their own session for carrying around all the information for the app. However, launching independent instances of the same application by the same user means all instances for one user share a session. Using the metaphor above, you can think of shopping for separate people but having to use the same cart for everyone’s stuff. In the real world, you can make that work, but in the web world there can be problems. If the application was written to ship to only one shipping destination or if the application limits the quantity on an item for each purchase, then treating each shopping “instance” as a separate purchase, that is, using a separate session per purchase would work fine. You’re probably thinking, though, why not just change the application to allow multiple destinations or to allow a single purchase to be treated as multiple. If you are thinking this way, then you are at the perfect point to see the problem.
When our client asked us to help, there was a catch (as always there is). “We don’t have time to rewrite the application or even make enhancements. In fact, we don’t know specifically which use cases are even broken.” So, here we were with the real problem.
How do we just fix it without breaking anything else in the application? The real problem is that the applications were not designed to support multiple instances per session, but we needed a way to do it in JSF 2.0. The first approach, the one proposed by the client’s architect, was to use ViewScoped managed beans instead of SessionScoped. This would be a good answer since ViewScoped is per view, for example, each path through the shopping experience, and thus would avoid the problem altogether. But, of course, there is a catch; this requires you to write your application in a way that ViewScoped beans will work. Unfortunately these applications were not written that way. The other problem with this approach is that for some reason, using ViewScoped beans on pages that are still using the JSTL “c:foreach” tags causes major performance problems because each item being iterated gets its own scope and that isn’t what we wanted at all.
The Solution Process
Okay, so the applications were written in a way that expected there to be a session per instance, but the application server dictates that there is one session per user, not per application instance. JSF provides a request scope, but that won’t work because we don’t pass all the data on each request. We already know session scope won’t work. JSF 2.0 added view scope which also won’t work. But there is one more JSF scope (new in 2.0) that might work. It is called Custom scope. But as the name implies, you can make it do whatever you want it do, but you have to do the work. In our case, we did need something custom. We wanted it to behave just like session scope except that each time the same user launched a new instance of the application we wanted the application to act like we had our own separate piece of the session. Ah ha! What we wanted was a new scope called PartitionedSession scope. Each instance of the application would get its own session-like “cart” for holding all its information. The trick is how to make that work without rewriting the applications.
So the first part of the solution is implementing our new custom scope behind the scenes.
The basic premise behind JSF’s component-based architecture is that each time a page is rendered, whatever beans are referenced on the page are instantiated and populated, the page is displayed, and then the beans go away. The beans are instantiated by a class called an ELResolver. This resolver class looks at the scope of the bean to determine how to instantiate or find it. For request scope beans, the beans are always instantiated and initialized with request parameters. For session scope beans, the resolver looks in one place in session to see if a bean by that name already exists. If it does, that saved bean is used, otherwise a new instance is instantiated. View scoped beans are a little more complicated in that they depend on a view id being in the context so that the resolver knows which view scope to use. This design makes custom scope a cinch.
For JSF custom scope, you actually use a custom resolver. The custom resolver implements the normal ELResolver methods and is chained into the normal set of resolvers that are a part of the JSF framework. When a bean reference is being resolved, this custom resolver gets a chance to see if the custom scoped object is bound to already has an instance of the bean. In the SessionScoped case, a single hashmap per HttpSession is used to hold all the bean instances. Our custom resolver would need a way to have a separate hashmap per Partition. As long as the resolver has a way of knowing which partition to check, then it becomes a simple matter of picking the correct partition hashmap out of session. The solution to this part of the problem is to rely on a PartitionId. This id must be available to the resolver on every call, but given that assumption, the custom ELResolver is a very simple implementation of a hashmap lookup.
So, how do we ensure we always have a PartitionId?
Before answering that we must determine when to start using a PartitionId and when to stop using it. In our client’s scenario, any time an application is launched we want to get a new PartitionId and use it throughout that use of the application. In this case, a Partition life time is the same as that of the browser window it was opened into. There was some debate on whether to use the term WindowId instead of PartitionId, but since this is a server solution, PartitionId was chosen. For all intents and purposes, there is a one-to-one correspondence of PartitionId to browser window. So the trick is that every time an application is launched, the server needs to know to create a new unique PartitionId. Every subsequent request made from that browser window needs to pass in that same PartitionId to let the resolver know which partition to use.
Without going into all the details, it was decided that it is simplest to treat the case where there is no PartitionId on the request as the case where we are launching an application. In this case, the custom resolver will ask a PartionedScope service to generate a new unique PartitionId (unique for this user) and will then use this new id to create a new hashmap in session for this new partition. Once again I’ll point out that the custom resolver is very simple.
The difficult part is how we ensure that that PartitionId will come in on every subsequent request from that window. The problem is that when a response is rendered to a screen, it contains a number of user input controls that allow the user to call back to the server. The most obvious controls are buttons and links. For instance, in our shopping cart example, after adding something to the cart, the user may see a button for “Check-out”, a link for “Shipping Options”, images for previous and next page, and many images for shopping items. All of these cases cause a new request to the server and to use the information stored in our session partition, we must pass the PartitionId on every possible link. This problem is further complicated by AJAX requests that can asynchronously update the page.
Using a common practice of adding a hidden field to a page to carry information from page to page would be very time-consuming, tedious and error-prone.
We needed an alternative that would be done programmatically.
As with all successful solutions, we “re-used” an existing implementation. As it turns out, this problem had already been solved by two existing JSF frameworks; JBoss Seam and MyFaces Orchestra. Each of these open source frameworks used the term Conversation rather than partition, but both faced similar challenges and both came up with similar solutions. The solution we borrowed was to use a Servlet filter to override the encodeUrl call. This call is made when rendering the response to ensure that every link on the resulting response will have a URL that is properly “escaped” so that spaces and other special characters don’t interfere with the html parsing of the resulting page. Using this call as a “hook”, we were able to add the PartitionId, regardless of whether it was created or passed in on the request, to every link on the response page. Again, this solution is very clean and simple, avoiding the need to change any application pages.
As simple as this approach sounds, it didn’t come to us instantly. Also, this wasn’t the only requirement that required ingenuity. Another closely related requirement was how to consistently handle session timeout. It too required a servlet filter solution and in the end was combined with the PartitionedScope solution since both solutions were really just hacks needed to hold the application together until the application could be re-designed to support these requirement correctly.
In the end, our approach went from requirement specification to implementation in a very short four week sprint. This challenge of making the application behave in a way that it was not designed to handle without any pervasive application changes was a difficult one, but not that uncommon.
I want to thank Josh McKinzie for his expert participation in this solution, since without his determination, perseverance and skills, this solution would not have been possible, especially in the time frame given. While there was a lot of stress and long hours required for our success, in the end it was an enjoyable experience that I had a lot fun doing. Satisfying nearly impossible requests from our clients is what makes our jobs rewarding.
– by Keith Shakib, firstname.lastname@example.org