[Note: I originally posted this problem/solution to Stackoverflow.com]
Problem:
I am upgrading an application from these component versions to their latest counterparts:
1 2 3 4 |
Spring 3.0.4 -> Spring 4.1.6 Hibernate 3.3.0 -> Hibernate 4.3.8 Spring Webflow 2.0.7 -> Spring Webflow 2.4.1 Spring Security 2.0.4 -> Spring Security 3.2.6 |
I am currently very stuck on a problem related the OpenSessionInViewFilter
and Spring Webflows. None of the code pertaining to my webflow is even executed, the problem occurs upon initialization of the webflow and Hibernate SessionHolder. I have not changed the webflow configuration during this Spring/Hibernate upgrade and everything has been working fine in production for almost 6 years. I am getting the following exception for which there is little help available on the web. The stack trace is a mile long, so I am including what I think are the important parts.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
2015/04/06 18:39:31 ERROR exception_jsp Stack Trace - org.springframework.webflow.execution.FlowExecutionException: Exception thrown in state 'null' of flow 'process/order' at org.springframework.webflow.engine.impl.FlowExecutionImpl.wrap(FlowExecutionImpl.java:573) at org.springframework.webflow.engine.impl.FlowExecutionImpl.start(FlowExecutionImpl.java:227) at org.springframework.webflow.executor.FlowExecutorImpl.launchExecution(FlowExecutorImpl.java:140) at org.springframework.webflow.mvc.servlet.FlowHandlerAdapter.handle(FlowHandlerAdapter.java:238) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893) ... org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:122) at ... org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91) at ... at java.lang.Thread.run(Thread.java:745) Caused by: java.lang.IllegalStateException: Already value [org.springframework.orm.hibernate4.SessionHolder@30b921ac] for key [org.hibernate.internal.SessionFactoryImpl@486fe7cb] bound to thread [http-nio-8080-exec-8] at org.springframework.transaction.support.TransactionSynchronizationManager.bindResource(TransactionSynchronizationManager.java:190) at org.springframework.webflow.persistence.HibernateFlowExecutionListener.bind(HibernateFlowExecutionListener.java:250) at org.springframework.webflow.persistence.HibernateFlowExecutionListener.sessionStarting(HibernateFlowExecutionListener.java:137) at org.springframework.webflow.engine.impl.FlowExecutionListeners.fireSessionStarting(FlowExecutionListeners.java:117) at org.springframework.webflow.engine.impl.FlowExecutionImpl.start(FlowExecutionImpl.java:367) at org.springframework.webflow.engine.impl.FlowExecutionImpl.start(FlowExecutionImpl.java:223) ... 76 more |
Pertinent parts of web.xml:
1 2 3 4 5 6 7 8 9 10 |
<filter> <filter-name>openSessionInViewFilter</filter-name> <filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class> <init-param><param-name>singleSession</param-name><param-value>false</param-value></init-param> </filter> <filter-mapping> <filter-name>openSessionInViewFilter</filter-name> <url-pattern>*.htm</url-pattern> </filter-mapping> |
Pertinent parts of Application Context configuration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping"> <property name="order" value="0"/> <property name="flowRegistry" ref="flowRegistry"/> </bean> <!-- Dispatches requests mapped to flows to FlowHandler implementations --> <bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter"> <property name="flowExecutor" ref="flowExecutor"/> </bean> ... <bean id="flowExecutionListener" class="org.springframework.webflow.persistence.HibernateFlowExecutionListener"> <constructor-arg ref="sessionFactory" /> <constructor-arg ref="transactionManager" /> </bean> ... <webflow:flow-executor id="flowExecutor"> <webflow:flow-execution-listeners> <webflow:listener ref="flowExecutionListener" /> <webflow:listener ref="securityFlowExecutionListener" /> </webflow:flow-execution-listeners> </webflow:flow-executor> ... <webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices" base-path="/WEB-INF"> <webflow:flow-location-pattern value="/**/*-flow.xml" /> </webflow:flow-registry> |
Webflow configuration file:
1 2 3 4 5 6 7 8 9 10 11 |
<flow xmlns="http://www.springframework.org/schema/webflow" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.4.xsd"> <persistence-context/> <on-start> <evaluate expression="orderFormFactory.createOrderForm(externalContext.sessionMap.inboxCriteria)" result="flowScope.orderForm"/> </on-start> ... |
It seems that the OpenSessionInViewFilter
is creating the Hibernate Session first, then the HibernateFlowExecutionListener
is attempting to create one rather than using the one created by the OpenSessionInViewFilter
thus causing the "Already value [org.springframework.orm.hibernate4.SessionHolder@xxxxxxxx] for key [org.hibernate.internal.SessionFactoryImpl@xxxxxxxx] bound to thread [xxxxxxxxxxxxxxx]"
error.
Any ideas on what I can tweak or further delve into for troubleshooting? Any solutions or workarounds? Anyone else seen this? Thank you for the help!
Solution
My solution to this was to split out my Web Flows from being handled by the OpenSessionInViewFilter. Before I made the change, all URLs in the app that ended in *.htm were routed through the OpenSessionInViewFilter. This was my web.xml before the changes:
1 2 3 4 5 6 7 8 9 10 |
<filter> <filter-name>openSessionInViewFilter</filter-name> <filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class> <init-param><param-name>singleSession</param-name><param-value>false</param-value></init-param> </filter> <filter-mapping> <filter-name>openSessionInViewFilter</filter-name> <url-pattern>*.htm</url-pattern> </filter-mapping> |
Now my web.xml has this configuration:
1 2 3 4 5 6 7 8 9 10 11 |
<filter> <filter-name>openSessionInViewFilter</filter-name> <filter-class>org.somepackage.MyCustomOpenSessionInViewFilter</filter-class> <init-param><param-name>singleSession</param-name><param-value>false</param-value></init-param> </filter> <filter-mapping> <filter-name>openSessionInViewFilter</filter-name> <url-pattern>*.htm</url-pattern> <url-pattern>*.flow</url-pattern> </filter-mapping> |
Notice that I added a new *.flow url-pattern to my filter-mapping for the openSessionInViewFilter and I now have a custom class that overrides the Spring OpenSessionInViewFilter (more on that below). I then had to change the URL suffix of the places in my views (and controllers in a few cases) where I invoked webflows from using ‘.htm’ to using ‘.flow’.
So, for example, if I had previously used webflow url of ‘/customer/customer-add.htm’, I changed it to ‘/customer/customer-add.flow’.
The next piece is that I added a class derived from Spring’s OpenSessionInViewFilter whose purpose was to pass all *.htm URLs to the OpenSessionInViewFilter while simply forwarding all *.flow URLs to the next filter in the chain (thereby allowing the HibernateFlowExecutionListener to eventually handle the URL and manage the session properly):
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package org.somepackage; import javax.servlet.http.HttpServletRequest; import org.springframework.orm.hibernate4.support.OpenSessionInViewFilter; public class MyCustomOpenSessionInViewFilter extends OpenSessionInViewFilter { @Override public boolean shouldNotFilter( HttpServletRequest request ) { return request.getRequestURI().contains( ".flow" ); } } |
Finally, please note that you should leave the Spring configuration you already have for the HibernateFlowExecutionListener alone — no changes required there:
1 2 3 4 |
<bean id="flowExecutionListener" class="org.springframework.webflow.persistence.HibernateFlowExecutionListener"> <constructor-arg ref="sessionFactory" /> <constructor-arg ref="transactionManager" /> </bean> |