State Management and the
PortletSession
If the user is going to be kind enough to provide
information to us through html forms and http headers, the least we could do is
keep track of that information, if only for the duration of the users visit to
our site.
To provide a stateful experience for the portal user,
portlet developers rely upon the services of the PortletSession, an object that
is remarkably similar to the HttpSession object from the Servlet API.
The PortletSession is easy to use, and helps maintain a
stateful experience for the user.
This chapter will look at the PortletSession, how the
PortletSession works, and will even discuss some of the drawbacks and
alternatives to using the PortletSession object.
The Transient
Nature of the PortletSession
PortletSessions and User Portlets
Leveraging the PortletSession Object
The NumberGuesser and the PortletSession
Tips for Manipulating Objects Stored in the PortletSession
Inspecting the BettetNumberGuesser Portlet
When Form Data is Absent
Session Management When Form Data is Present
Always Remove Unneeded Data from the PortletSession
Debriefing the BetterNumberGuesser’s JSP
Pages
Using the PortletRequest Scope
A Word About the PortletRequest Object
Multiple Scopes Exist for Passing JavaBeans Around
The PortletRequest Object is the Most Efficient Scope
Using PortletSession in the
betternumberform.jsp
Cleverly Using the <portletAPI:createURI/> Custom Tag
Ending the Number Guessing Game
The correct.jsp
The State Management Challenge
The Unsexy PortletUnavailable Error Message
The Blight of Non-Standard User Navigations
Managing State with the PortletSession Object
When a user visits our site, we quite often need to keep track of
information the user has provided us. Information stored in the PortletRequest
or PortletResponse object are purged as soon as a response is sent back to the
client, which creates a problem when we want to keep track of information that
a user has provided us on previous request-response cycles.
To store user specific information for the duration of their
interaction with the server, the Portal API provides a special object called
the PortletSession.
Any useful piece of information, in the form of serializable Java
objects that is, can be stuffed inside of a user’s session object. That
information is then available to our portlet on all subsequent request-response
cycles. The PortletSession is effectively tied to the user for which it was
created.
The Transient Nature of the
PortletSession
One thing to note about the PortletSession is that it is
transient. If the user leaves our site, doesn’t interact with the site for a
predetermined amount of time (usually thirty minutes), or even if the user
closes their browser, information stored in the PortletSession is lost, or at
least, is no longer tied to the user.
Information stored in the PortletSession is not stored
persistently. The job of the PortletSession is to simply create a stateful
experience for users interacting with our portlet during the current visit to
our website.
PortletSessions and User Portlets
Another interesting aspect of a PortletSession is that it is
local to the portlet that created it. Data stored in the PortletSession of one
portlet cannot be shared by other portlets on the same portal page. If there
are four portlets on a portal page, if they all use a PortletSession object, then
each one will have a separate, unshared PortletSession.
Many developers believe that data stored in the PortletSession is
available to all the portlets a user might access. This is not true. A
PortletSession is tied to the specific portlet on the specific portal page for
which the PortletSession was created.
Leveraging the PortletSession Object
Here’s a simple portlet that keeps track of the number of times
the doView method of a portlet is called.
The view count is stored in the session. Every time the doView
method of the portlet is called, the view count increases by one. The view
count is then displayed back to the user.
|
Figure ?-?
|
|
package
com.pulpjava.example.session;
import
java.io.*;import org.apache.jetspeed.portlet.*;
public class
SessionSample extends PortletAdapter {
public void doView(PortletRequest request,
PortletResponse response)
throws
PortletException, IOException {
//grab the
PortletSession out of the PortletRequest
PortletSession session = request.getPortletSession();
//pull the
visitCount out of the session. On the first visit, this will be null
String visitCount =
(String)session.getAttribute("timesvisited");
PrintWriter out = response.getWriter();
//if this is the
first time viewing the portlet, set the timevisited value to 1
if (visitCount==null){
session.setAttribute("timesvisited", ""+1);
out.print("Wecome to our little
portlet!");
out.print("<BR>Click
refresh, restore, minimize or maximize!!!");
}
//if this portlet
has been visited before, increase the count
else {
int newCount =
Integer.parseInt(visitCount) + 1;
session.setAttribute("timesvisited", "" +
newCount);
out.print("Number of times visiting
this portlet: ");
out.print(newCount);
}
}
}
|
The NumberGuesser and the PortletSession
Our NumberGuesserPortlet could really use a good dose of the
PortletSession object.
Currently, our NumberGuesserPortlet, from figure ?-?, thinks of a
number, and asks the user to guess it, but it doesn’t give the user a chance to
keep trying until they guess the magic number.
By incorporating the PortletSession object into the
NumberGuesserPortlet, we could store both the magic number and the number of guessing
attempts. We could even give the user a hint as to whether they should guess
higher or lower.
When the user guesses the magic number successfully, we can even
send them to a JSP page that indicates that they have successfully guessed the
magic number, and displays the number of attempts it took.
Our improved NumberGuesserPortlet appear in Figure ?-?
Tips for Manipulating Objects Stored
in the PortletSession
Information is pulled out of the PortletSession using the
getAttribute(String) method. Objects are put into the PortletSesison object
using the setAttribute(String, Object) method.
The name used to put an object into the session must match
exactly the name used to pull the object out. Spelling mistakes, or even a
different casing of letters will cause the return of a null object. I always
suggest using all lower case letters for the names of objects being placed into
the session, if only to create a consistent standard.
The PortletSession also includes a method called
removeAttribute(Object). If you no longer have use for an object you have
stored in the PortletSession, that object should be removed from the session.
The management of session data is one of the most significant
performance bottlenecks your portal server will encounter. Eliminate unnecessary
objects from your PortletSession, and trying to avoid what is known as ‘session
bloat’ will help minimize performance problems at runtime.
Figure ?-? We
can easily as PortletSession support to our NumberGuesser
|
|
package
com.pulpjava.betterguesser;
import
java.io.IOException; import
org.apache.jetspeed.portlet.*;
public class BetterNumberGuesser extends PortletAdapter {
public void
doView(PortletRequest request, PortletResponse response)
throws
PortletException, IOException {
PortletContext context =
getPortletConfig().getContext();
PortletSession session =
request.getPortletSession();
if
(request.getParameter("number") == null) {
int magicNumber =
(int)(System.currentTimeMillis() % 9) + 1;
session.setAttribute("magicnumber", new
Integer(magicNumber));
session.setAttribute("guesses", "0");
request.setAttribute("message", "Guess the
number!");
context.include("betternumberform.jsp", request, response);
} else {
Integer magicNumber = (Integer)
session.getAttribute("magicnumber");
String guesses =
(String)session.getAttribute("guesses");
guesses = "" +
(Integer.parseInt(guesses)+1);
session.setAttribute("guesses", guesses);
Integer guess = new
Integer(request.getParameter("number"));
if (guess.intValue() >
magicNumber.intValue()) {
request.setAttribute("message", "Guess lower!");
context.include("betternumberform.jsp", request, response);
}
if (guess.intValue() <
magicNumber.intValue()) {
request.setAttribute("message", "Guess higher!");
context.include("betternumberform.jsp", request, response);
}
if (guess.intValue() ==
magicNumber.intValue()) {
session.removeAttribute("guesses");
session.removeAttribute("magicnumber");
}
}
}
}
|
Inspecting the BettetNumberGuesser Portlet
When Form Data is Absent
The first two lines of the BetterNumberGuesser portlet simply
declares the PortletSession and PortletContext object for easy access later on
in the code.
The next step is to see if form data is indeed coming to the
server. If there is no form data submitted, then we can assume the user is
looking at this portlet for the first time, or they are re-starting the
guessing process. In that case, we create a new magic number and stuff that
number into the PortletSession. We also set the number of guesses to zero,
after all, the number guessing process is just starting.
Once all of our objects have been initialized and stuffed into
the PortletSession, we can forward to the betternumberform.jsp page, but before
we do that, we stuff a little message into the PortletRequest. The message
simply says “Guess the number!!!” This message will be printed out by the JSP.
Figure ?-?
|
|
"""
public class BetterNumberGuesser extends PortletAdapter {
"""
if
(request.getParameter("number") == null) {
int magicNumber =
(int)(System.currentTimeMillis() % 9) + 1;
session.setAttribute("magicnumber",
new Integer(magicNumber));
session.setAttribute("guesses", "0");
request.setAttribute("message", "Guess the
number!");
context.include("betternumberform.jsp", request, response);
} else {
"""
}
|
Session Management When Form Data is Present
If request.getParameter(“number”) does not return null, then
indeed the user has just submitted a form, trying to guess the magic number. In
this case, we grab our magic number from the session, then we grab the number
of guesses from the session, and finally we grab the actual number guessed by
calling the request.getParameter(“number”) method.
If the number is too high, we send them to the
betternumberform.jsp file with a message stuffed into the request object
instructing them to guess lower.
request.setAttribute("message", "Guess
lower!");
If the number is too low, we send them to the
betternumberform.jsp file with a message stuffed into the request object
instructing them to guess higher.
request.setAttribute("message", "Guess
higher!");
If the user guesses the magic number correctly, we delegate to
the correct.jsp, which indicates that the user has successfully guessed the
magic number.
Always Remove Unneeded Data from the
PortletSession
After rendering the correct.jsp, we also remove the magic number
and the number of guesses from the session. After all, they are not needed once
the magic number has been guessed. If the user wants to play again, we’ll just
generate a new number, and set their number of guesses to zero.
Figure ?-?
|
|
"""
if
(guess.intValue() == magicNumber.intValue()) {
context.include("correct.jsp",
request, response);
session.removeAttribute("guesses");
session.removeAttribute("magicnumber");
}
"""
|
Debriefing the BetterNumberGuesser’s JSP
Pages
There are two JSP pages used in this example, the
betternumberform.jsp, and the correct.jsp. Let’s look at the
betternumberform.jsp page.
Figure ?-?
|
|
<%@
taglib uri="/WEB-INF/tld/portlet.tld" prefix="portletAPI"
%>
<portletAPI:init
/>
<FORM
action="<portletAPI:createURI/>">
I'm thinking
of a number between 1 and 10.<BR><BR>
<I><%=request.getAttribute("message")%></I>
<INPUT
name="<portletAPI:encodeNamespace
value="number"/>" size="10"
type="text" />
<INPUT
name="<portletAPI:encodeNamespace value="submit"/>"
value="Guess!!" type="submit" />
</FORM>
Number of
guesses: <%=session.getAttribute("guesses")%><BR>
<A
HREF="<portletAPI:createURI/>">Start Over</A>
|
Using the PortletRequest Scope
The biggest change between new betternumberguesserform.jsp and
the one used earlier in figure ?-?, is
the presence of the <%=request.getAttribute(“message”)%> expression just
before the input field.
Inside the BetterNumberGuesser portlet, a String message is
placed in the request scope, indicating whether a user should guess higher or
lower. This message is then printed out just before the textfield in the
betternumberguesser.jsp page. This scriptlet allows one JSP page to be used
dynamically, and render itself slightly differently depending upon the state of
the application.
A Word About the PortletRequest Object
While the PortletRequest’s main purpose in life is to describe
the incoming request, it can be also used to pass information from a portlet to
a JSP, or from one JSP to another JSP.
Multiple Scopes Exist for Passing
JavaBeans Around
Just as we can store Java objects in a user’s PortletSession, we
can store Java objects in the PortletRequest as well. In fact, the process is
so similar, the methods for storing attributes in the PortletSession and the
PortletRequest are the same:
portletRequest.setAttribute(String key, Object
value);
portletSession.setAttribute(String key, Object value);
The PortletRequest Object is the Most
Efficient Scope
The big difference between the PortletRequest and PortletSession
as a means for passing data between a Servlet and a JSP is the duration of the
data stored. Information stuffed into a PortletSession will last as long as the
user remains at our site, whereas data shoved into a PortletRequest is purged
as soon as a response is sent back to the client; or more accurately, once the
service method of the Portlet has finished execution.
The PortletRequest is the perfect scope to use when you want to
share a Java object between a portlet and JSP, but you don’t want that data to
hang around beyond the current request-response cycle, as it would if it was
stored in the PortletSession.
Use the PortletRequest scope. It’s memory efficient and
resource-friendly.
Using PortletSession in the
betternumberform.jsp
Our improved html form from our betternumberform.jsp also
displays to the user how many guesses they have made at the magic number, based
on the ‘guesses’ key placed in the PortletSession during the doView method.
Number
of guesses: <%=session.getAttribute("guesses")%>
Finally, our improved form provides a link for the user to start
the number guessing game over again.
<A HREF="<portletAPI:createURI/>">Start
Over</A>
Notice how we are again using the portletAPI:createURI custom
tag, but this time the tag is used to create an anchor link, rather than
specifying an action target for a form.
Cleverly Using the
<portletAPI:createURI/> Custom Tag
This use of the portletAPI:createURI custom tag will create a
link in the portlet that when clicked, will cause the portal page to re-render
itself. Of course, since it was a link that triggered the page to be displayed,
and not a form submission, the doView method will reset the number of guesses
to zero, and generate a new magic number, since no guessed number will be sent
to the server. Pretty clever, eh?
Ending the Number Guessing Game
The correct.jsp file compliments our improved form, and
represents the end of the number guessing game.
Figure ?-? The correct.jsp
|
|
<%@
taglib uri="/WEB-INF/tld/portlet.tld" prefix="portletAPI"
%>
<portletAPI:init
/>
You got it
correct!
<BR><BR>
The magic
number was <%=session.getAttribute("magicnumber")%>
<BR><BR>
Number of guesses:
<%=session.getAttribute("guesses")%>
<BR><BR>
Click <A
HREF="<portletAPI:createURI/>">here</A> to start
over.
|
The correct.jsp
The correct.jsp page simply displays a congratulatory ‘You got it
correct!’ message, and then displays the magic number, along with the number of
attempts it took for the user to come up with the correct answer.
A link back to the portlet is also used to allow the user to play
the number guessing game again.
The State Management Challenge
NOTE: When the number guessing portlet is completed, if you refresh
the page, you will get an error. This is due to the fact that a refresh causes
previously submitted forms to be resubmitted.
The Unsexy PortletUnavailable Error
Message
Of course, when our game is completed, the magic number is pulled
out of the session. This will trigger a doView method that has form data, but
does not have the number of guesses, or a magic number. Either of the following
two lines:
Integer.parseInt(guesses)
magicNumber.intValue()
will trigger a null pointer exception, causing a ‘Portlet
Unavailable’ message to be delivered back to the user.
To avoid this problem, our doView method should not only check
for form data, but also check to make sure the magicnumber key in the
PortletSession object is not null.
A more robust beginning to our portlet might look like this:
Object formData =
request.getParameter("number");
Object sessionData =
session.getAttribute("guesses");
if (formData==null || sessionData==null) {…..}
The Blight of Non-Standard User
Navigations
The portal provides many opportunities for non-standard
navigations by the user, perhaps even more than a typical Servlet and JSP
application allows. We must be aware of all of these non-standard navigations,
and do our best to create robust portlets that do not fail when a user does
unexpected things.
Never doubt this truism: as idiot-proof as we make our
application, the world will keep creating better idiots. Thoroughly test your
applications for non-standard user navigations.