Issue Details (XML | Word | Printable)

Key: SWF-92
Type: New Feature New Feature
Status: Resolved Resolved
Resolution: Fixed
Priority: Major Major
Assignee: Keith Donald
Reporter: Keith Donald
Votes: 23
Watchers: 26
Operations

If you were logged in you would be able to see more operations.
Spring Web Flow

Add Flow Managed Persistence Context Support for Hibernate and JPA

Created: 22/Mar/06 07:01 PM   Updated: 21/Aug/07 06:06 AM   Resolved: 15/Aug/07 03:55 PM
Component/s: Integration: Persistence Support
Affects Version/s: 1.0
Fix Version/s: 2.0 M1

Time Tracking:
Original Estimate: 5d
Original Estimate - 5d
Remaining Estimate: 0d
Remaining Estimate - 0d
Time Spent: 5d 0h 40m
Time Spent - 5d 0h 40m

File Attachments: 1. Java Source File ExtendedFlowExecutionListenerInterceptor.java (11 kB)
2. Zip Archive mylyn-context.zip (1.0 kB)
3. Zip Archive OpenSessionInFlow-exampleImpl.zip (16 kB)
4. Zip Archive OpenSessionInFlow-exampleImplUpdated.zip (32 kB)
5. Java Source File OpenSessionInFlowListenerInterceptor.java (17 kB)

Issue Links:
Depends
 


 Description  « Hide
Implementation of the long session per conversation pattern. To be implemented:
- Hibernate-based (session per conversation)
- JPA (JSR-220) based (managed persistence context)

Patterns:
- Stateful Session Bean Pattern
- Flow Listener Pattern

Andrew Ebaugh added a comment - 24/Mar/06 06:51 PM
Attaching a listener/interceptor that can be used for long sessions in webflows. The two java files are the main classes; the zip file has these, and the various support classes needed to get it running as-is. I don't anticiapte it depends on more than: hibernate 3, swf 1.0ea, Spring 1.2.7, log4j

See the javadocs for info and example config, specifically the javadoc for OpenSessionInFlowListenerInterceptor.java

I'll post a comment with general issues I ran into. Also, this version is adapted from an earlier one that worked with previous versions of swf. I'll comment on how it got harder with 1.0 ,as well as 1.0's potential for simplifying things.

Andrew Ebaugh added a comment - 24/Mar/06 07:46 PM
Some issues I ran into on this:
-supporting lazy initialization
Since a view is rendered by the DispatcherServlet after the FlowController has finished processing (and hence, after the requestProcessed method is called for FlowExecutionlisteners), a session has to be kept open longer than the flow session is active in order to support lazy initialization within the view objects. This is something we needed, and so moved the disconnecting of a session out to a handler interceptor postHandle method.

-supporting webflow continuations
The ability to load a serialized flowExecution to continue at a flow at a particular point leads to some interesting questions on how it should be handled with a long hibernate session. Since the persistence context and the flow scope are two separate storage locations that might share object references, if the hibernate session is not kept inside the flowExecution, any differences between the state of objects in the flowExecution (flow scoped objects) and the state of the persistence context needs to be reconciled. If they are kept together, then the hibernate session has to be serializable. Neither of these seemed very attractive. The SessionStorage implementations attached are meant to represent these two options, one storing the hibernate externally in the http session, the other storing it in the flow scope. This brings up the following two issues.

-keeping flow scoped objects in sync with objects in the persistence context
The way I use it now, the state of objects in the persistence context "wins out" over that of the flow scope. When the flow session becomes active, the listener has an option to iterate through options in the flow scope and try to get the copy from the persistence context. In that way, even if you skip back several steps in a webflow, the object(s) you are working with will reflect the most recent state. I'm still testing what the result of that will be; for my application, it doesn't seem to be of consequence.

-serializing/deserializing hibernate objects
When you serialize and deserialize objects that reside in the persistence context, you end up with different object references, and disconnected persistent collections. For example, you have an object graph in flow scope that you just pulled from a session.get call. The flow scope is serialized out to continuation storage, then deserialized and you pull the object out of flow scope. You will have a different object reference than prior to serializing (and the one in the persistence context), yet if you tried to session.save the object, you probably would get a non-unique object exception because it generates the same persistence key. To complicate things more, any persistent collections in the object graph have a null session reference when deserialized. If you try and access unloaded elements, exceptions are thrown. This was addressed in the same way as above: when the session becomes active, the objects in flow scope are traded for those in the persistence context, which also produces functional collections.

-new (unpersisted) flow objects
One of my primary uses for webflow was as a "wizard" that lead users through creating a new object graph that would eventually be saved to the database. The previous issues I layed out make this more difficult with long sessions that spans several activations/deactivations of the hibernate session. The method for iterating through the flow scope, replacing objects with their persistence context copy, doesn't work if the object is not yet in the persistence context. Worse yet, if that new object has persistent objects attached to the object graph you are building, they will quickly lose sync with the persistence contex through serialization/deserialization, and attempts to save the new object at the end of the flow will fail. My solution for this was to persist objects early, and not flush them to the db until the transaction is committed. This can be done using session.persist() early (provided your id generation mechanism does not require that the entity be saved to the database before an id is generated), and a session with on "COMMIT" flush mode. Again, for my situation, this was not a problem, but in some cases it could be.

I hope this is information someone find valuable. It would be great to hear ideas on other solutions to any of these problems.

Matt Raible added a comment - 19/Sep/06 09:15 AM
Keith - is this patch being considered? If so, can you assign a "fix version" to this issue? Thanks.

Donnchadh O Donnabhain added a comment - 15/Jan/07 07:12 AM
Andrew - have you got any unit tests to go along with this code? I've started using it and would like to create some unit tests, but it would be great if some already exist.

Donnchadh O Donnabhain added a comment - 15/Jan/07 07:48 AM
Updated for SWF 1.0

jacob added a comment - 17/Jan/07 06:48 PM
Donna -- If you have your unit tests working, please share them here (along with the configuration files, if possible). Thanks!

Andries Inzé added a comment - 05/Apr/07 03:42 PM
I've been using this openSessionInFlowInterceptor for couple of weeks, and things are looking great.
However, I had some problem getting it to work, with the update example posted here.
Here is my 'setup', hope it helps someone!

The following setup is in a Spring MVC - Spring WebFlow setup. Since the OpenSessionInView and OpenSessionInFlow can't exist side by side, I had to split them up.

<!--
        ###########################
        Maps url's to controllers
        No webflow's
        ###########################
    -->
    <util:properties id="urlMapping-mappings"
        location="classpath:xxx/xxx/xxx/web/urlMapping-controllerMappings.properties" />
    <bean id="urlMapping"
        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping ">
        <property name="mappings" ref="urlMapping-mappings" />
        <property name="order" value="1" />
        <property name="interceptors">
            <list>
                <ref bean="openSessionInViewInterceptor" />
            </list>
        </property>
    </bean>

    <!--
        ###########################
        Maps url's to controllers
        Webflow's
        ###########################
    -->
    <util:properties id="urlMapping-mappings-flows"
        location="classpath:xxx/xxx/xxx/web/urlMapping- flows-controllerMappings.properties" />
    <bean id="urlMapping-flows"
        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings" ref="urlMapping-mappings-flows" />
        <property name="order" value="2" />
        <property name="interceptors">
            <list>
                <ref bean="openSessionInFlowListenerInterceptor" />
            </list>
        </property>
    </bean>
    
    <!--
        ###########################
        Interceptors
        ###########################
    -->
    <bean name="openSessionInViewInterceptor"
        class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor">
        <property name="sessionFactory">
            <ref bean="sessionFactory" />
        </property>
    </bean>

    <bean id="openSessionInFlowListenerInterceptor"
        class="org.springframework.webflow.session.OpenSessionInFlowListenerInterceptor ">
        <property name="sessionStorage" >
            <bean class="org.springframework.webflow.session.impl.HttpSessionBackedSessionWrapperStorage" />
        </property>
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>


from applicationContext-controller.xml

<!-- ################### -->
    <!-- WEBFLOW CONTROLLERS -->
    <!-- ################### -->

    <bean name="customerFlowController"
        class="org.springframework.webflow.executor.mvc.FlowController">
        <property name="flowExecutor" ref="flowExecutor" />
        <property name="defaultFlowId" value="customer-flow" />
    </bean>


    <bean id="flowExecutor"
        class="org.springframework.webflow.config.FlowExecutorFactoryBean ">
        <property name="definitionLocator" ref="flowRegistry" />
        <property name="repositoryType" value="CONTINUATION" />
        <property name="executionListenerLoader" ref="listenerLoader"/>
    </bean>

    <bean id="flowRegistry"
        class="org.springframework.webflow.engine.builder.xml.XmlFlowRegistryFactoryBean">
        <property name="flowLocations">
            <list>
                <value>/WEB-INF/flows/xxx/**-flow.xml</value>
            </list>
        </property>
    </bean>

    <bean id="listenerLoader"
        class="org.springframework.webflow.execution.factory.ConditionalFlowExecutionListenerLoader">
        <property name="listeners">
            <map>
                <entry>
                    <key><ref bean="openSessionInFlowListenerInterceptor" /></key>
                    <value>*</value>
                </entry>
            </map>
        </property>
    </bean>

Ben Hale added a comment - 09/May/07 09:30 AM
Added a HibernateSessionPerConversationListener. Add the SessionFactory to the listener and the listener to the executor. All standard Spring/Hibernate behavior will work. Both native Hibernate and HibernateTemplate repositories will participate in the session and transaction created by the listener.

Revision 7064

Donnchadh O Donnabhain added a comment - 17/May/07 06:56 AM
Hi Ben, nice work.

A couple of questions:
i) Does the session remain bound for the rendering of the view? it looks like it doesn't.
ii) How will this work in an AJAX scenario? access the hibernate session would need to be serialised

In both of these cases HibernateSessionPerConversationListener could be extended to provide the required functionality but this may be something to keep in mind.

William Parker added a comment - 04/Jun/07 05:18 AM
Can the updated patch, (including HibernateSessionPerConversationListener), of this enhancement be made available before 1.1 release? If not, when is 1.1 due to be released?

Apologies if this update is available - I cant seem to locate it. The attachments above our out-dated.

William Parker added a comment - 05/Jun/07 06:33 AM
Ignore the above - I've accessed the snapshot.

Erwin Vervaet added a comment - 19/Jun/07 02:46 PM
A comment from the forums (by Will Parker):

Hi

With regards to the above class which is still in beta version...

Shouldn't the exceptionThrown() method rollback the transaction? Currently it calls unBindSession() which performs a commit().

Should the lines in exceptionThrown() be changed from:

Session hibSession = getHibernateSession(context);
unBindSession(hibSession);


to....


Session hibSession = getHibernateSession(context);
hibSession.getTransaction().rollback();
TransactionSynchronizationManager.unbindResource(h ibSession);


Keith Donald added a comment - 08/Aug/07 11:03 AM
Enhancing for SWF 1.1 m1