I'm not quite sure I've understood you.
Basically, the problem with my filter is that if there's a forward, it will re-run the filter again. Is this right?
So, given Filters A, B, C, and servlet S:
Request comes in, goes through A, B, C, then S. S causes a forward, which causes a second run through A, B, C, S without unwinding the old stack. I.E. Final stack looks like this: A, B, C, S, A, B, C, S. Is this right?
I always thought that the filters were not re-run, only the servlet level was re-run. (i.e. stack goes A, B, C, S, backup, S again).
I can see how in this case the filters will run twice and cause two "opens" (which are guarded, so only one is opened really), and two closes.
If I make a ThreadLocal counter for the filter depth, will this ensure that I know when I'm at the last instance on the filter chain?
Also, I'm not sure what you mean by wrapping a session to throw Exception on close. It already throws exceptions as it is, so how would forcing it to throw an exception help any?
Would the following filter work:
Code:
public class HibernateSessionFilter implements Filter {
static final ThreadLocal threadLocal = new ThreadLocal();
public void init(FilterConfig filterConfig) throws ServletException {}
public void destroy() {}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws
IOException,
ServletException
{
try {
incrementCount();
Session hsession = HibernateHelper.currentSession();
hsession.setFlushMode(FlushMode.NEVER);
// Pass control on to the next filter
chain.doFilter(request, response);
}
catch (HibernateException ex) {
throw new ServletException(ex);
}
finally {
try {
if(decrementCount())
HibernateHelper.closeSession();
}
catch (HibernateException ex) {
// Should I throw this or ignore?
throw new ServletException(ex);
}
}
}
// Increment the thread-local counter
private void incrementCount() {
Integer callCount = (Integer)threadLocal.get();
if(null == callCount) {
threadLocal.set(new Integer(1));
} else {
threadLocal.set(new Integer(callCount.intValue() + 1));
}
}
// Decrement the thread-local counter and return true only if
// counter is 0.
private boolean decrementCount() {
Integer callCount = (Integer)threadLocal.get();
if(null == callCount) // should not happen
return false;
threadLocal.set(new Integer(callCount.intValue() - 1));
return callCount.intValue() == 0;
}
}
Actually, one other thing I just remembered:
Code:
HibernateHelper:
private static final ThreadLocal session = new ThreadLocal();
public static Session currentSession() throws HibernateException {
Session s = (Session) session.get();
// Open a new Session, if this Thread has none yet
if (s == null) {
s = getSessionFactory().openSession();
session.set(s);
}
return s;
}
public static void closeSession() throws HibernateException {
Session s = (Session) session.get();
session.set(null);
if (s != null && s.isOpen())
s.close();
}
On calling closeSession on HibernateHelper, it sets the ThreadLocal value (called session.. I think I'll change that name!) to null, so the reference to the closed session is gone.
That being the case, it should be impossible for HibernateHelper to ever close the same hibernate session twice.
Not only that, but I'm very clearly checking to see if the hibernate session is open before attempting to close. If a call to Hibernate's Session.isOpen() returns true, shouldn't a Session.close() succeed without throwing an exception?