(From SpringSource forum.)
When the HttpSession
has expired and the user re-submits a page in the flow, he/she is sent back to the beginning of the flow. All I want to add to this behavior is a message explaining why it occurred. "You were inactive, so you have been restarted..."
What's the easiest/best-practice way to do this?
The default behavior, in FlowHandlerAdapter.defaultHandleException(), "attempts to start a new execution of the ended or expired flow".
It looks like a WebFlow way to handle this would be to provide a FlowHandler
with a handleException() method that checks for an instanceof NoSuchFlowExecutionException
, then do something like construct a redirect URL or place something on Session scope that can later be removed once utilized.
Due to the way WebFlow uses redirects, I don't think any other scopes would allow such a flag or message to be used later when the new flow's view renders.
However, simply detecting a new Session in an Interceptor
or even a Filter
would seem to be just as effective. Which is what I ended up doing in my previous investigation of this, as documented in the referenced forum thread. I was just hoping for something prettier.
Also, by the time the new flow begins, a new Session ID has already been created, so there's no way to initially detect this condition from within the flow.xml.
Sample filter logic:
if (request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid()) {
log.info("Expired Session ID: " + request.getRequestedSessionId());
response.sendRedirect("sessionExpired");
}
else {
chain.doFilter(request, response);
}
Sample Interceptor:
public class SessionExpiredInterceptor extends HandlerInterceptorAdapter
{
private String redirectLocation = "sessionExpired";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception
{
if (request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid())
{
response.sendRedirect(redirectLocation);
return false;
}
return true;
}
public String getRedirectLocation() {
return redirectLocation;
}
public void setRedirectLocation(String redirectLocation) {
this.redirectLocation = redirectLocation;
}
}
Step 1:
FlowController has a default handlerAdapter. To customize session exceptions you are required to write your own custom handler adapter and register it with the flow controller bean as below:
<bean id="flowController" class="org.springframework.webflow.mvc.servlet.FlowController">
.
.<property name="flowHandlerAdapter" ref="customFlowHandlerAdapter"/>
.
</bean>
<bean id="customFlowHandlerAdapter" class="gov.mo.courts.pbw.adapters.CustomFlowHandlerAdapter"
p:flowExecutor-ref="flowExecutor"/>
Step 2:
CustomFlowHandlerAdapter
In this class override defaultHandleException method. this is the method that webflow invokes in case of exceptions and re-initializes session. please note, new session has already been created till this point. Only the exception type will tell you at this point that the previous session timed out.
public class PbwFlowHandlerAdapter extends FlowHandlerAdapter{
protected void defaultHandleException(String flowId, FlowException e,
HttpServletRequest request, HttpServletResponse response)
throws IOException {
if(e instanceof NoSuchFlowExecutionException){
if(e.getCause() instanceof NoSuchConversationException){
//"use newly created session object within request object to save your customized message."
}
}
super.defaultHandleException(flowId, e, request, response);
}
The first view page of your app should be able to show this message.
<%
if (session.getAttribute(YOUR_CUSTOM_MSG_KEY) != null) {
%>
<p class="errormessage">
<%=session.getAttribute(YOUR_CUSTOM_MSG_KEY)%>
</p>
<%
//once the message has been shown, remove it from the session
//as a new session has already been started at this time
session.removeAttribute(YOUR_CUSTOM_MSG_KEY);
}
%>
Hope this helps.
From the Jira request I had opened, WebFlow developer says,
Custom FlowHandler is the extension point for an expired session. You
can redirect to the flow and append a query param, something like
restarted=true, and then in the flow declare an <input
name="restarted" type="boolean" value="flashScope.restarted" />
.
I haven't been able to confirm, but wanted to share the information.